Skip to content

01 Reverse P Invoke and Delegate Lifetime

win edited this page Mar 9, 2015 · 1 revision

from msdn https://msdn.microsoft.com/en-us/magazine/cc164193.aspx

The CLR allows you to pass a delegate to the unmanaged world so that it can call the delegate as an unmanaged function pointer. In fact, what's happening is that the CLR creates a thunk, which forwards the calls from native code to the actual delegate, then to the real function (see Figure 10).

Figure 10 Using a Thunk (Click the image for a larger view)

Usually, you won't have to worry about the lifetime of delegates. Whenever you are passing a delegate to unmanaged code, the CLR will make sure the delegate is alive during the call. However, if the native code keeps a copy of the pointer beyond the span of the call and intends to call back through that pointer later, you might need to use GCHandle to explicitly prevent the garbage collector from collecting the delegate. We must warn you that a pinned GCHandle could have a significantly negative impact on program performance. Fortunately, in this case, you don't need to allocate a pinned GC handle, because the thunk is allocated in the unmanaged heap and is referencing the delegate indirectly through a reference known to the GC. Therefore, it is not possible for the thunk to move around, and native code should always be able to call the delegate through the unmanaged pointer if the delegate itself is alive. Marshal.GetFunctionPointerForDelegate can convert a delegate to a function pointer, but it doesn't do anything to guarantee the lifetime of the delegate. Consider the following function declaration:

public delegate void PrintInteger(int n); 
[DllImport(@"MarshalLib.dll", EntryPoint="CallDelegate")] 
public static extern void CallDelegateDirectly( IntPtr printIntegerProc);

If you call Marshal.GetFunctionPointerForDelegate for it and store the returned IntPtr, then pass the IntPtr to the function you are going to call, like so:

IntPtr printIntegerCallback = Marshal.GetFunctionPointerForDelegate( new     
Lib.PrintInteger(MyPrintInteger));
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 
CallDelegateDirectly(printIntegerCallback);

It is possible that the delegate will be collected before you call CallDelegateDirectly, and you will get an MDA error that CallbackOnCollectedDelegate was detected. To fix that, you can either store a reference to the delegate in memory or allocate a GC handle. If native code returns an unmanaged function pointer to the CLR, it is the responsibility of native code to keep the actual function code around. Usually this isn't a problem unless the code is in a dynamically loaded DLL or generated on the fly.