在32位中编组结构时的行为与64位运行时中的行为不同

时间:2015-07-16 16:02:53

标签: c# winapi data-structures pinvoke marshalling

我在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'堆栈不平衡。这很可能   因为托管的PInvoke签名与非托管签名不匹配   目标签名。检查调用约定和参数   PInvoke签名与目标非托管签名匹配。

但是在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);

1 个答案:

答案 0 :(得分:5)

x64调用约定非常与x86约定不同。您可以在this MSDN page中找到概述。必不可少的部分是:

  

任何不适合8个字节或不是1,2,4或8个字节的参数必须通过引用传递。

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