在 32 位和 64 位运行时封送结构时的不同行为

Different behavior when marshaling structures in 32 bit than in 64 bit runtimes

我在 PInvoking 时发现了这个 SetupDiCreateDeviceInfoList

C++ 函数签名是:

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

在 C# 中,我定义了这样的 GUID 结构:

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

和这样的函数:

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

由于在 C# 中结构默认通过副本传递(与 类 不同),因此该函数签名不应匹配。事实上,在 32 位运行时调用函数时:

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

我得到一个错误:

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.

但是在 64 位运行时上面的代码可以工作。为什么???

当然,如果我改为通过引用传递结构,该函数在 32 位和 64 位运行时都能正常工作:

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

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

x64 调用约定与 x86 约定非常不同。您会在 this MSDN page 中找到概述。重要部分是:

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

x64 编译器在必要时强制执行此要求,创建结构的副本并在程序按值传递此类结构时传递指向它的指针。在这种情况下,pinvoke 编组器会处理它。所以,没有堆栈不平衡。