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

本文关键字:结构 32位 64位 运行时 | 更新日期: 2023-09-27 18:03:08

我在pinvocation 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签名不匹配目标的签名。的呼叫约定和参数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);

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

x64调用约定与x86约定非常不同。您将在此MSDN页面中找到概述。关键部分是:

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

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