【问题标题】:Determine C# P/Invoke Structure Alignment at Runtime在运行时确定 C# P/Invoke 结构对齐
【发布时间】:2015-03-28 17:17:42
【问题描述】:

我正在尝试为一些 Windows setupapi 调用编写良好的 P/Invoke 签名,但我在打包 setupapi 的结构时遇到了以下问题:

// Excerpt from setupapi.h
#if defined(_WIN64)
#include <pshpack8.h>   // Assume 8-byte (64-bit) packing throughout
#else
#include <pshpack1.h>   // Assume byte packing throughout (32-bit processor)
#endif

现在,这意味着我不能只将 StructLayoutAttribute.Pack 属性设置为常量值。

我尝试了以下操作:

[StructLayout(LayoutKind.Sequential, Pack = Environment.Is64BitProcess ? 8 : 1)]
public struct SP_DEVINFO_DATA
{
    public uint cbSize;
    public Guid ClassGuid;
    public uint DevInst;
    public IntPtr Reserved;
}

正如预期的那样,这会失败并出现以下编译错误:

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

我真的很想避免#if 并设置不同的编译平台,而不是Any CPU。我可以在运行时确定 C# 结构的打包吗?

【问题讨论】:

    标签: c# pinvoke setupapi


    【解决方案1】:

    不,打包是一个编译时概念,因为它决定了(除其他外)类型的整体大小。这在运行时无法更改。

    在这种情况下,如果您不愿意为 x86 和 x64 单独构建,则必须自己进行编组。感谢@Hans Passant 提供了一种干净的方式来实现这一目标:

    • 定义两个结构,比如SP_DEVINFO_DATA32SP_DEVINFO_DATA64
    • 为每个结构定义一个方法重载
    • 在运行时,选择要创建的结构并调用适当的重载。

    看起来像这样:

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct SP_DEVINFO_DATA32 { /* stuff */ }
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct SP_DEVINFO_DATA64 { /* stuff */ }
    
    [DllImport("setupapi.dll")]
    public static extern void Function(ref SP_DEVINFO_DATA32 data);
    
    [DllImport("setupapi.dll")]
    public static extern void Function(ref SP_DEVINFO_DATA64 data);
    
    public void DoStuff()
    {
        if (Environment.Is64BitProcess)
        {
            var data = new SP_DEVINFO_DATA64 
            { 
                cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA64)) 
            };
            Function(ref data);
        }
        else
        {
            var data = new SP_DEVINFO_DATA32 
            {
                cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA32)) 
            };
            Function(ref data);
        }
    }
    

    【讨论】:

    • 不需要 IntPtr hack,您可以简单地声明采用其他结构类型的函数的重载。
    • @HansPassant 提供的调整听起来相当优雅。我们要把它添加到答案中吗?
    【解决方案2】:

    我在 x86 和 x64 都用 C++ 和 C# 做过,我会说“pack”对那个结构没有任何影响,所以你只需要

    [StructLayout(LayoutKind.Sequential)]
    

    在 x86 中,C++ 和 C# 结构的 sizeof/Marshal.SizeOf 为 28 字节,在 x64 中为 32 字节。如果我创建一个

    [StructLayout(LayoutKind.Sequential)]
    public struct MyStruct
    {
        public SP_DEVINFO_DATA a;
        public SP_DEVINFO_DATA b;
    }
    

    在 C 中

    struct MyStruct
    {
        SP_DEVINFO_DATA a;
        SP_DEVINFO_DATA b;
    };
    

    x86 的大小为 56,x64 的大小为 64(所以正好是双零填充)。 x86 和 x64 的大小差异是 4 个字节,所以 IntPtr 的大小差异正是如此。

    【讨论】:

      猜你喜欢
      • 2010-11-18
      • 2012-01-21
      • 2012-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多