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

Does a C# method need to be pinned when used as Win32 callback?

我正在将 C# 实例方法传递给 Win32 API 调用,稍后将用作 Windows 到我的应用程序的回调函数。当我传递对对象的引用时,该引用会暂时固定,直到调用 returns(请参阅 Jason Clark 的 this article)。

如果 API 调用将保留地址供以后调用后使用 returns,我必须在调用之前显式固定对象(我可以通过 [= 从非托管内存分配它17=]).

但是 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 表示,“...确保委托实例的生命周期涵盖非托管代码的生命周期;否则,委托在被垃圾回收后将不可用”这是完全有道理的,因为如果没有托管代码引用,class 可能会被卸载,导致方法 ("delegate") 不再在内存中。

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

换句话说:如果定义 MidiInProcess 的 class 保持加载状态,我可以确定上面的 MidiInProcess 方法在 [=14= 之后不会更改地址吗? ] returns,还是我必须采取一些步骤来固定它?

更新

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

    private MidiInProc midiInProc;

    ...

    midiInProc = MidiInProcess;

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

来自 MSDN 文章 How to: Marshal Callbacks and Delegates By Using C++ Interop:

Notice that is it possible, but not necessary, to pin the delegate using pin_ptr (C++/CLI) to prevent it from being re-located or disposed of by the garbage collector. Protection from premature garbage collection is needed, but pinning provides more protection than is necessary, as it prevents collection but also prevents relocation.

If a delegate is re-located by a garbage collection, it will not affect the underlaying managed callback, so Alloc is used to add a reference to the delegate, allowing relocation of the delegate, but preventing disposal. Using GCHandle instead of pin_ptr reduces fragmentation potential of the managed heap.

重点是我的。

当然,它与使用 P/Invoke 相关,而不仅仅是使用 C++ IJW 互操作,正如 Hand 在评论中所说和 Chris Brumme said in the blog post 我在评论中链接,但这是最好的文档,我觉得。

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