当用作Win32回调时,是否需要固定C#方法?

我正在将一个C#实例方法传递给Win32 API调用,该调用稍后将用作从Windows到我的应用程序的回调函数。 当我传递对象的引用时,该引用被临时固定,直到调用返回(参见Jason Clark的这篇文章 )。

如果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); 

在一个存档的教程中 ,微软表示,“……确保委托实例的生命周期涵盖非托管代码的生命周期;否则,代理在垃圾收集后将无法使用。” 这很有道理,因为如果没有托管代码引用,类可能会被卸载,从而导致方法(“委托”)不再存在于内存中。

但是,如果在堆上分配了对象,那么重定位方法的可能性如何呢? 方法的地址可能会在其生命周期内发生变化吗?

换句话说:如果定义MidiInProcess的类仍然加载,我可以确定上面的MidiInProcess方法在midiInOpen返回后不会改变地址,还是我必须采取一些步骤来固定它?

UPDATE

根据Hans的第一条评论,上面传递给midiInOpen的代表是短暂的,并且不能保证以后在调用时可用(因为在托管代码端没有持久的引用)。 我相信在封闭实例的私有成员中保留对它的引用应足以使其保持活动状态,前提是只要可能需要回调,对封闭实例本身的引用就会保留在应用程序的其他位置。 虽然不完整,但这可能是这样的:

  private MidiInProc midiInProc; ... midiInProc = MidiInProcess; int hResult = midiInOpen( out hHandle, deviceID, midiInProc, // Pass the reference you retained. 0, CALLBACK_FUNCTION); 

从MSDN文章如何:使用C ++ Interop Marshal回调和委托 :

请注意,可以(但不是必须)使用pin_ptr(C ++ / CLI)来锁定委托,以防止它被垃圾收集器重新定位或处理。 需要防止过早的垃圾收集,但钉扎提供了比必要的更多保护 ,因为它可以防止收集,但也可以防止重新定位。

如果通过垃圾收集重新定位委托,它将不会影响底层托管回调 ,因此Alloc用于添加对委托的引用,允许重定位委托,但防止丢弃。 使用GCHandle而不是pin_ptr可以降低托管堆的碎片潜力。

重点是我的。

当然,它与使用P / Invoke相关,而不仅仅是使用C ++ IJW互操作,正如Hand在评论中所说 , Chris Brumme在评论中链接的博客文章中说,但我认为这是文档中最好的。

如果您足够关心,可以在文档中提交错误。 它现在托管在GitHub上,所以它可能比过去更容易。