We’ve got P/Invoke doing some heavy lifting of managed-to-unmanaged signature translation. The opposite direction is surprisingly easy as well.
Typically, you would encounter the need for unmanaged code to call managed code as part of a callback. (There is the scenario where unmanaged code is the interop initiator, which we will discuss in another post.)
In the unmanaged world, callbacks are function pointers; in the managed world, callbacks are delegates. Recall that a delegate “knows” not only the method that needs to be called, but also the target object – and indeed, you can create delegates that reference an instance method on a specific object. On the other hand, a function pointer is just that – a pointer – you can’t use a single pointer to store information about the method and the target object.
Obviously, a conversion is in place, and this conversion is also handled automatically by P/Invoke. For example, suppose you want to call the EnumWindows Win32 API to enumerate all the windows on the system (and perhaps retrieve their text using the awesome GetWindowText wrapper we developed earlier). The function’s signature is:
BOOL WINAPI EnumWindows( __in WNDENUMPROC lpEnumFunc, __in LPARAM lParam );
Let’s ignore the second parameter for now, and consider the first one. It’s a function pointer, specifically to a function that has the following signature:
BOOL CALLBACK EnumWindowsProc( __in HWND hwnd, __in LPARAM lParam );
What’s the calling convention here? CALLBACK is a typedef for stdcall, which is the default Win32 calling convention.
We would like to pass a delegate to the EnumWindows function instead of a function pointer, or at least treat a managed delegate as a function pointer. There are two ways of doing this. The harder way would be to declare EnumWindows as a method that takes a function pointer for its first parameter, and then obtain manually that function pointer from a managed delegate:
[DllImport("user32")] public static extern bool EnumWindows( IntPtr lpEnumFunc, uint lParam); [UnmanagedFunctionPointer] public delegate bool EnumWindowsProc( IntPtr hWnd, uint lParam); EnumWindowsProc proc = new SomeClass().SomeMethod; IntPtr fptr = Marshal.GetFunctionPointerForDelegate(proc); EnumWindows(proc, 0); GC.KeepAlive(proc);
OK, so what’s going on here? First of all, our P/Invoke signature takes a pointer – very sad. Next, we have a delegate that matches the EnumWindowsProc signature. The [UnmanagedFunctionPointer] attribute is not strictly required in this case, but you should know that it can be used to customize the calling convention of the obtained delegate, as well as other things.
Finally, to call the actual entry point, we create a delegate and obtain a function pointer from it using Marshal.GetFunctionPointerForDelegate. That’s the function pointer we pass to EnumWindows. All that’s left is to exercise extra caution, and make sure the delegate is not garbage collected while the EnumWindows method executes. Fortunately, it executes synchronously, so the GC.KeepAlive call after the EnumWindows call would ensure that the delegate is not collected under any circumstances.
If this whole delegate-being-collected stuff seems nonsensical, take a look at the MDA that detects this kind of bugs – CallbackOnCollectedDelegate.
You might be wondering how Marshal.GetFunctionPointerForDelegate overcomes the problem of converting a pair <method,target> to a single pointer. Indeed, what typically happens is that a small stub is generated on the fly – this stub’s address is the unmanaged function pointer, and what it does is call the method on the appropriate object, which is hardcoded into it.
The easier approach would be to let P/Invoke do the conversion from a managed delegate to the unmanaged function pointer, and would work just as well:
[DllImport("user32")] public static extern bool EnumWindows( EnumWindowsProc lpEnumFunc, uint lParam); [UnmanagedFunctionPointer] public delegate bool EnumWindowsProc( IntPtr hWnd, uint lParam); EnumWindowsProc proc = new SomeClass().SomeMethod; EnumWindows(proc, 0); GC.KeepAlive(proc);
Still, it is the caller’s responsibility to make sure the delegate is not collected during the unmanaged call.