2

I discovered this when I was PInvoking SetupDiCreateDeviceInfoList.

The C++ function signature is:

HDEVINFO SetupDiCreateDeviceInfoList(
  _In_opt_ const GUID *ClassGuid,
  _In_opt_       HWND hwndParent
);

In C# I have defined the GUID structure like this:

[StructLayout(LayoutKind.Sequential)]
public struct GUID
{
    public uint Data1;
    public ushort Data2;
    public ushort Data3;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] Data4;
}

and the function like this:

[DllImport("Setupapi.dll")]
public static extern IntPtr SetupDiCreateDeviceInfoList(GUID ClassGuid, IntPtr hwndParent);

Since in C# structures are passed by copy by default (unlike classes) this function signature should not match. And indeed when calling the function on 32-bit runtime:

GUID classGuid = new GUID();
IntPtr deviceInfoSet = SetupDiCreateDeviceInfoList(classGuid, IntPtr.Zero);

I get an error:

SetupDiCreateDeviceInfoList' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

But on 64-bit runtime the code above works. Why ???

Of course, if I pass the structure by reference instead, the function works correctly on both 32-bit and 64-bit runtimes:

[DllImport("Setupapi.dll")]
public static extern IntPtr SetupDiCreateDeviceInfoList(ref GUID ClassGuid, IntPtr hwndParent);

GUID classGuid = new GUID();
IntPtr deviceInfoSet = SetupDiCreateDeviceInfoList(ref classGuid, IntPtr.Zero);
Chris
  • 1,213
  • 1
  • 21
  • 38
  • @PeterDuniho On x64 I receive a valid pointer to a device info set. I developed this on 64-bit environment, and the the driver installed successfully every time. I only discovered the bad function signature when I decided to test on a 32-bit environment. – Chris Jul 16 '15 at 16:30

1 Answers1

8

The x64 calling convention is very different from the x86 conventions. You'll find an overview in this MSDN page. Essential part is:

Any argument that doesn’t fit in 8 bytes, or is not 1, 2, 4, or 8 bytes, must be passed by reference.

An x64 compiler enforces this requirement when necessary, creating a copy of the struct and passing a pointer to it when the program passes such a struct by value. In this case the pinvoke marshaller takes care of it. So, no stack imbalance.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536