Common gotchas when writing your own p/invoke


If you're looking to get into some p/invoke action, you'd be well-served to check out the pinvoke wiki to see if somebody else has done it too. If what you need isn't there, you may end up forced to write your own, and here are some gotchas I've seen people run into:

  • C++ bool and Win32 BOOLEAN are not the same as C# bool (aka System.Boolean). In Win32, BOOL is a 4-byte type, and BOOLEAN is a 1-byte type. [See also MadQ's remarks about VARIANT_BOOL.] Meanwhile, C++ bool is not standardized by Win32, so the size will vary based on your compiler, but most compilers use a 1-byte value. And then C# is even weirder: The bool is a 1-byte type, but it marshals as a 4-byte type by default.

  • Win32 char is not the same as C# char (aka System.Char). In C#, char is a Unicode character (two bytes), whereas in C/C++ under Win32 it is an ANSI character (one byte).

  • Win32 long is not the same as C# long (aka System.Int64). In C#, long is 64-bit value, whereas in C/C++ under Win32 it is a 32-bit value.

  • If memory is allocated and freed across the interop boundary, make sure both sides are using the same allocator. It is my understanding that the CLR uses CoTaskMemAlloc/CoTaskMemFree by default. If your Win32 function doesn't use CoTaskMemAlloc, you'll have to teach the CLR which allocator you really want.

  • When laying out structures, you have to watch out for alignment.

That last one is particularly gnarly on 64-bit systems, where alignment requirements are less forgiving than on x86. The structure declarations on pinvoke.net tend to ignore 64-bit issues. For example, the declaration of the INPUT structure (as of this writing—it's a wiki so it's probably changed by the time you read this) reads as follows:

[StructLayout(LayoutKind.Explicit)]struct INPUT {
  [FieldOffset(0)] int type;
  [FieldOffset(4)] MOUSEINPUT mi;
  [FieldOffset(4)] KEYBDINPUT ki;
  [FieldOffset(4)] HARDWAREINPUT hi;
}

This structure layout is correct for 32-bit Windows, but it's incorrect for 64-bit Windows.

Let's take a look at that MOUSEINPUT structure, for starters.

typedef struct tagMOUSEINPUT {
    LONG    dx;
    LONG    dy;
    DWORD   mouseData;
    DWORD   dwFlags;
    DWORD   time;
    ULONG_PTR dwExtraInfo;
} MOUSEINPUT, *PMOUSEINPUT, FAR* LPMOUSEINPUT;

In 64-bit Windows, the LONG and DWORD members are four bytes, but the dwExtraInfo is a ULONG_PTR, which is eight bytes on a 64-bit machine. Since Windows assumes /Zp8 packing, the dwExtraInfo must be aligned on an 8-byte boundary, which forces four bytes of padding to be inserted after the time to get the dwExtraInfo to align properly. And in order for all this to work, the MOUSEINPUT structure itself must be 8-byte aligned.

Now let's look at that INPUT structure again. Since the MOUSEINPUT comes after the type, there also needs to be padding between the type and the MOUSEINPUT to get the MOUSEINPUT back to an 8-byte boundary. In other words, the offset of mi in the INPUT structure is 8 on 64-bit Windows, not 4.

Here's how I would've written it:

// This generates the anonymous union
[StructLayout(LayoutKind.Explicit)] struct INPUT_UNION {
  [FieldOffset(0)] MOUSEINPUT mi;
  [FieldOffset(0)] KEYBDINPUT ki;
  [FieldOffset(0)] HARDWAREINPUT hi;
};

[StructLayout(LayoutKind.Sequential)] struct INPUT {
  int type;
  INPUT_UNION u;
}

I introduce a helper structure to represent the anonymous union that is the second half of the Win32 INPUT structure. By doing it this way, I let somebody else worry about the alignment, and it'll be correct for both 32-bit and 64-bit Windows.

static public void Main()
{
  Console.WriteLine(Marshal.OffsetOf(typeof(INPUT), "u"));
}

On a 32-bit system, this prints 4, and on a 64-bit system, it prints 8. The downside is that you have to type an extra u. when you access the mi, ki or hi members.

input i;
i.u.mi.dx = 0;

(I haven't checked what the PInvoke Interop Assistant comes up with for the INPUT structure.)

Comments (21)
  1. Anonymous says:

    The interop assistant comes up with basically the same thing you did, only with lengthier names and uints for the DWORDs

  2. nathan_works says:

    Since my C# development uses mostly C# features (WCF of recent), or uses interop to access functions from internal/own C++ DLLs, I’ve not needed interop to get to native win32 APIs..  How often do other folks need to do it?

    I mean, the questions in my mind are: how common is it ? Why isn’t there similar functionality in the .net CLR ? Why not use C++ to access the win32 api to begin with ?

  3. Anonymous says:

    To answer "Nathan works" – I have had to use a lot of interop from C#. A quick search shows that I use 154 different calls in my interop library.

    (We have special needs because we use Windows XP embedded on touch-screen kiosk-like devices.)

    For example, we have the taskbar on our machines set to auto-hide and be behind other applications. Yet we’d still get occasional reports from the field of the taskbar appearing over our full-screen application. We couldn’t repro it, but it had to be fixed.

    I’m sure the solution would make Raymond cringe – I use interop to get the taskbar window handle then make it invisible. Then it will never appear over anything. This would be really, really bad on a desktop app, but fixed our custom machines.

    In fact, as a regular reader of Raymond’s blog, I often come across situations where I wonder "how much would this make Raymond cringe." Sometimes that prompts me to find a better solution. But sometimes I just have to get things done within the deadline.

    Moving the interop to C++ would still require a layer of interop between my C# app and the C++ helper app, so that wouldn’t really buy us anything.

  4. Anonymous says:

    As a reply to "Shaved Regressor". Why on earth do you run Explorer.exe on a kiosk machine to begin with? You should change the shell to be your own kiosk app.

  5. Anonymous says:

    Koro, I don’t remember why they decided to stick with Explorer as the shell for our machines. There were discussions, but our OS guy decided keep Explorer.

    Anyway, the example I used was just one thing among many. Most of the interop I have to use isn’t for dealing with Explorer.

  6. Anonymous says:

    I once "gained" access to a kiosk (like, could run IE, the MSN Messenger included with XP, browse folders and such) because the taskbar had somehow been displayed over the kiosk app. Funny thing was that the kiosk was a pay-per-minute internet kiosk.

    About interop, yeah, that’s because most of the time, the .NET Framework / Windows Forms does not support natively doing what we are trying to do. Especially in the area of the shell (like for example getting an icon off the system image list, and using said system imagelist with a ListView), it seems as soon as you want to do something interesting, you HAVE to PInvoke…

  7. Anonymous says:

    About a year ago I was playing around with streaming webcam video. It surprised me how little support the .NET framework has for this.

  8. Anonymous says:

    Helpful reminder: the C# "struct INPUT" should be annotated with [StructLayout(LayoutKind.Sequential)]. Normal JIT behavior to lay out structs and classes in any order it desires (which in many versions happens to be the same as Sequential, but I understand may change without warning.

  9. Anonymous says:

    Clearly, Raymond’s backlog is about 1.5 years, as this post (or so he seems to indicate in the link above) was written on January 24, 2008.

  10. Anonymous says:

    Isn’t there an attribute to explicitly declare the byte-packing for a struct?  I know I’ve used it before.

  11. Anonymous says:

    Karellen: but Win32 was traditionally based in C.  MS didn’t need a bool type, as it had BOOL standardised.  However, various other houses had settled on enum/int/char, which is why C++ tried to fix it.

    As for your final question, VC4.2 for one.

  12. Anonymous says:

    By the way, name decoration is of course internal to Microsoft’s linker, but DLL imports, COM and RPC are well defined, and they’re more the ABI.

  13. Anonymous says:

    I was just about to post what aaron said above; there’s a sequential struct alignment missing, I think.

  14. Anonymous says:

    "Meanwhile, C++ bool is not standardized by Win32, so the size will vary based on your compiler, but most compilers use a 1-byte value."

    Huh? Given that the Win32 API and the Windows implementation of it is written by Microsoft, and /de facto/ reference compiler for the platform is the compiler which is also written by Microsoft, surely the Win32 ABI is by definition whatever the MS compiler does for the MS implementation of the MS API.

    There was an interesting essay which touches on this subject of C++ ABIs, their various origins and relative usage, posted to the Qt Labs Blogs the other day, which seems to have come to the conclusion that MS defines the C++ ABI for Win32/Windows. <http://labs.trolltech.com/blogs/2009/08/12/some-thoughts-on-binary-compatibility/&gt;

    Just out of curiosity, which compilers do *not* implement the C++ bool type as a 1-byte value when compiling for the Win32 target?

  15. Anonymous says:

    Gah. System.Char is not a ‘Unicode character’ – it is a UTF16LE code unit (and the MSDN documentation could use correcting).

    The difference may not seem important at first glance – but I’m sure people who mis-use the Win32 API and think it’s OK because it works for them didn’t think that understanding the technology they were using was important either.

    [See What(‘s) a character! The article uses the term “character” in its common meaning of “storage character” or “code unit” rather than “linguistic character” or even “code point”. -Raymond]
  16. Anonymous says:

    On of the Biggest problems I had was interacting with the DDK accross 32/64 bit platforms pack value is 1 for 32bit but for 64bit platforms it’s 8

       [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]

       public struct SP_DEVICE_INTERFACE_DETAIL_DATA_W_32

       {

           public int cbSize;

           [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]

           public string DevicePath;

       }

       [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)]

       public struct SP_DEVICE_INTERFACE_DETAIL_DATA_W_64

       {

           public int cbSize;

           [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]

           public string DevicePath;

       }

    This is a simple sturct but it causes problems all over the place

  17. Anonymous says:

    “In Win32, BOOL is a 4-byte type, and BOOLEAN is a 1-byte type.”

    You might want to add that VARIANT_BOOL is is a 2-byte type where 0 == FALSE and -1 == TRUE.

    This is very important when declaring COM interfaces. For interfaces, the CLR marshals System.Boolean as a 2-byte type. A lot of COM interfaces use and mix the Win32 BOOL and VARIANT_BOOL. If the IDL (or the C/C++ header) declares a BOOL, make sure to decorate it with with a MarshalAsAttribute(UnmanagedType.Bool) in your .NET code.

    [Excellent points, thanks for contributing. I’ve linked to your comment from the main article. -Raymond]
  18. Anonymous says:

    Full ACK on pinvoke.net – whatever I tried to grab from there didn’t work. Been P/Invoking for more than two years without the site and my code happily runs on 32- and 64-bit systems.

    @Raymond: Good summary of what you have to pay attention to!

  19. Anonymous says:

    @Nathan_works: Several years ago, I hand-rolled a large portion of the DirectShow API in C#. I needed the ability to manually build and manipulate filter graphs.

    I also once wrote a pluggable protocol handler in C# (IInternetProtocol, etc.) to allow reading resources from .NET assemblies. Kinda like the res: protocol lets you read Win32 resources from executable files.

    None of this would have been possible without InterOp.

  20. Anonymous says:

    @Raymond: By that definition, C/C++ char is also a "Unicode character", since it stores UTF-8 code units just dandy.  I agree that a pendantic definition of ‘character’ vs code-point vs whatever isn’t needed, but "UTF-16" is in order to meaningfully describe the difference between C char, .NET System.Char === C# char, and gcc wchar32_t

  21. Anonymous says:

    Also, the part about C/C++ char being ANSI is just plain wrong.  Win32 functions variously treat char strings as ANSI, the OEM code page, or any of a large number of other SBCS or MBCS formats.

Comments are closed.