Stevens Miller Stevens Miller - 1 month ago 9
C# Question

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

I am passing a C# instance method to a Win32 API call that will later be used as a callback function from Windows into my application. When I pass a reference to an object, that reference is temporarily pinned until the call returns (see this article by Jason Clark).

If the API call will retain the address for later use after the call returns, I have to pin the object explicitly before the call (I can allocate it from unmanaged memory via Marshal.AllocHGlobal, or I can pin down a managed object via GCHandle.Alloc).

But what about methods retained for use as callbacks by the Win32 API? Specifically, I have this code:

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


At an archived tutorial, Microsoft says, "...make sure the lifetime of the delegate instance covers the lifetime of the unmanaged code; otherwise, the delegate will not be available after it is garbage-collected." That makes perfect sense, as the class might be unloaded if there are no managed-code references to it, resulting in the method ("delegate") no longer being in memory.

But what about the possibility of relocation of the method, as there is of objects allocated on the heap? Might the method's address change during its lifetime?

In other words: Provided that the class defining
MidiInProcess
remains loaded, can I be certain that the
MidiInProcess
method, above, will not change address after
midiInOpen
returns, or must I take some step(s) to pin it?

UPDATE

As per Hans's first comment, the delegate passed to
midiInOpen
, above, is ephemeral and is not guaranteed to be available later when called (because there's no persistent reference to it on the managed code side). I believe retaining a reference to it in a private member of the enclosing instance should be enough to keep it alive, provided that a reference to the enclosing instance itself is kept somewhere else in the application for as long as the callback might be needed. Although incomplete, this is how that might look:

private MidiInProc midiInProc;

...

midiInProc = MidiInProcess;

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

Answer Source

From the MSDN article 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.

Emphasis is mine.

Sure, it's relevant to using P/Invoke and not only using C++ IJW interop, as Hand said in the comments and Chris Brumme said in the blog post I linked in the comments, but that's the best the documentation has, I think.

You can file a bug with the documentation if you care enough. It's hosted on GitHub now so it's probably easier in than in the old days.