Welcome to MSDN Blogs Sign in | Join | Help

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

Published Thursday, August 13, 2009 7:00 AM by oldnewthing
Filed under:

Comments

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 11:35 AM by Random832

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

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 1:52 PM by Nathan_works

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 ?

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 2:49 PM by Shaved Regressor

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 2:57 PM by Koro

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 4:11 PM by Shaved Regressor

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 5:38 PM by Koro

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...

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 6:40 PM by James Schend

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

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 7:17 PM by aaron

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 7:27 PM by Ian

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 7:29 PM by Dan

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

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 7:53 PM by Karellen

"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/>

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

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 9:01 PM by Mark

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 9:23 PM by Mark

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.

# re: Common gotchas when writing your own p/invoke

Thursday, August 13, 2009 11:43 PM by Miral

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

# re: Common gotchas when writing your own p/invoke

Friday, August 14, 2009 7:07 AM by Joe

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]

# re: Common gotchas when writing your own p/invoke

Friday, August 14, 2009 7:24 AM by Garthy

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

# re: Common gotchas when writing your own p/invoke

Friday, August 14, 2009 1:35 PM by MadQ

"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]

# re: Common gotchas when writing your own p/invoke

Friday, August 14, 2009 2:52 PM by Ooh

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!

# re: Common gotchas when writing your own p/invoke

Sunday, August 16, 2009 6:13 AM by MadQ

@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.

# re: Common gotchas when writing your own p/invoke

Monday, August 17, 2009 4:19 PM by Ben Voigt [C++ MVP]

@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

# re: Common gotchas when writing your own p/invoke

Monday, August 17, 2009 4:22 PM by Ben Voigt [C++ MVP]

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.

New Comments to this post are disabled
 
Page view tracker