我正在将一个C#实例方法传递给Win32 API调用,该调用稍后将用作从Windows到我的应用程序的回调函数。当我传递对象的引用时,该引用会暂时固定,直到调用返回为止(请参阅Jason Clark的this article)。
如果API调用将在调用返回后保留地址供以后使用,我必须在调用之前明确地固定对象(我可以通过Marshal.AllocHGlobal从非托管内存中分配它,或者我可以确定托管对象通过GCHandle.Alloc)。
但是Win32 API保留用作回调的方法呢?具体来说,我有这个代码:
protected const int CALLBACK_FUNCTION = 0x30000;
private delegate void MidiInProc(
int handle,
uint msg,
int instance,
int param1,
int param2);
[DllImport("winmm.dll")]
private static extern int midiInOpen(
out int handle,
int deviceID,
MidiInProc proc,
int instance,
int flags);
private void MidiInProcess(
int hMidiIn,
uint uMsg,
int dwInstance,
int dwParam1,
int dwParam2)
{
}
...
int hResult = midiInOpen(
out hHandle,
deviceID,
MidiInProcess, // Might this move after the call returns?
0,
CALLBACK_FUNCTION);
在archived tutorial,Microsoft说,“...确保委托实例的生命周期涵盖非托管代码的生命周期;否则,代理将在垃圾回收后无法使用。”这很有道理,因为如果没有托管代码引用,类可能会被卸载,导致方法(“委托”)不再存在于内存中。
但是,如果在堆上分配了对象,那么重定位方法的可能性如何呢?方法的地址可能会在其生命周期内发生变化吗?
换句话说:如果定义MidiInProcess
的类仍然加载,我可以确定上面的MidiInProcess
方法在midiInOpen
返回后不会更改地址,或者我必须采取一些措施来确定它?
更新
根据Hans的第一条评论,上面传递给midiInOpen
的代表是短暂的,并且不能保证在以后被调用时可用(因为在托管代码端没有持久的引用)。我相信在封闭实例的私有成员中保留对它的引用应足以使其保持活动状态,前提是只要可能需要回调,对封闭实例本身的引用就会保留在应用程序的其他位置。虽然不完整,但这可能是这样的:
private MidiInProc midiInProc;
...
midiInProc = MidiInProcess;
int hResult = midiInOpen(
out hHandle,
deviceID,
midiInProc, // Pass the reference you retained.
0,
CALLBACK_FUNCTION);
答案 0 :(得分:2)
来自MSDN文章How to: Marshal Callbacks and Delegates By Using C++ Interop:
请注意,可以(但不是必须)使用pin_ptr(C ++ / CLI)来锁定委托,以防止它被垃圾收集器重新定位或丢弃。需要防止过早的垃圾收集,但固定可提供比必要更多的保护,因为它可以防止收集,但也可以防止重新定位。
如果通过垃圾收集重新定位委托,它将不会影响底层托管回调,因此Alloc用于添加对委托的引用,允许重定位委托,但防止处置。使用GCHandle而不是pin_ptr可以降低托管堆的碎片潜力。
重点是我的。
当然,它与使用P / Invoke相关,而不仅仅是使用C ++ IJW互操作,正如Hand在评论中所说,Chris Brumme said in the blog post我在评论中链接,但我认为这是文档中最好的。 / p>
如果您足够关心,可以提交文档错误。它现在托管在GitHub上,所以它可能比过去更容易。