With Windows 8 around the corner, managed code slowly taking over legacy systems written in C++, games developed in a mixture of .NET and C++, and the rest of this technology soup – I thought it would be a good time to provide a quick refresher of the available interoperability mechanisms between managed and unmanaged code. Nothing here is very new, but I get so many questions about it that at the very least I would have something to refer people to.
P/Invoke is best suited for managed code invoking global functions exported from C-style DLLs. For example, if your awesome WPF app needs just this one Win32 API, say AccessCheckByTypeResultListAndAuditAlarmByHandle, then you can declare a “managed” signature for it and call it as if it was a managed method.
Let’s take a look at a simpler example first. Suppose you want to call memcpy to copy around a chunk of memory (because you don’t trust Array.Copy). Well, you figure it’s exported from msvcrt.dll (the C runtime DLL), and then declare it like this:
[DllImport("msvcrt", CallingConvention=CallingConvention.Cdecl)] public static extern int memcpy( byte dst, byte src, uint len);
Now you can call this method from C# as if the unmanaged function indeed knew what managed byte arrays were. The P/Invoke layer creates the magic here: it takes managed byte arrays that reside on the GC heap, pins them (so that the GC can’t move them while they are accessed by memcpy), and passes a pair of pointers to memcpy. When memcpy returns, the P/Invoke layer unpins the managed byte arrays as if nothing happened.
A somewhat more complicated example, where the power of P/Invoke really shines, is when the memory allocation becomes tricky. For example, consider the GetWindowText Win32 API:
int WINAPI GetWindowText( __in HWND hWnd, __out LPTSTR lpString, __in int nMaxCount );
This function receives a string buffer that it is supposed to fill with the text retrieved from a particular window handle. Mapping the window handle to a managed type is easy – it’s just an opaque IntPtr returned by various window-management Win32 APIs. Now, the buffer itself is a pointer to a string that GetWindowText is supposed to fill out – however, recall that managed strings are immutable!
P/Invoke helps again here by accepting a StringBuilder where a mutable string is expected. In fact, the capacity of the StringBuilder instance is the buffer size we can pass as the third parameter, obtaining this managed code:
[DllImport("user32")] public static extern int GetWindowText( IntPtr hWnd, StringBuilder lpString, int nMaxCount); IntPtr hwnd = ...; StringBuilder text = new StringBuilder(100); GetWindowText(hwnd, text, 100);
Note that this time we didn’t specify the calling convention because GetWindowText is a standard Win32 API, and uses the stdcall calling convention, which is also P/Invoke’s default.
There are even more complex examples where you can use P/Invoke to manage the mapping of complex structures and in/out parameters. Nonetheless, the AccessCheckByTypeResultListAndAuditAlarmByHandle horror is not any less horrible – the function has 17 parameters, most of them pointers to various structures and arrays, and figuring out the mapping won’t be very easy.
As you see, P/Invoke does not absolve you from understanding the unmanaged function signature. It’s important that you understand the various function calling conventions, and how parameters can be mapped from managed types to unmanaged types. The great pinvoke.net community website is of immense assistance here, and can provide you a ready-made P/Invoke signature for almost anything. And then there’s also the Microsoft-made P/Invoke Signature Generator, which I mentioned here a couple of years ago.