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