在运行时确定 C# P/Invoke 结构对齐
Determine C# P/Invoke Structure Alignment at Runtime
我正在尝试为某些 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# 结构的打包吗?
不,打包是一个编译时概念,因为它决定了(除其他外)类型的整体大小。这不能在 运行 时间改变。
在这种情况下,如果您不愿意为 x86 和 x64 分别构建,则必须自己进行编组。感谢@Hans Passant 提供了一种干净的方法来实现这一目标:
- 定义两个结构,比如
SP_DEVINFO_DATA32
和SP_DEVINFO_DATA64
,
- 为每个结构定义一个方法重载
- 在 运行 时,select 创建哪个结构并调用适当的重载。
看起来像这样:
[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);
}
}
我在 C++ 和 C# 中完成过,包括 x86 和 x64,我会说 "pack" 对该结构没有任何影响,所以你只需要
[StructLayout(LayoutKind.Sequential)]
在 x86 上,C++ 和 C# 结构都有一个 28 字节的 sizeof
/Marshal.SizeOf
,在 32 字节的 x64 上。如果我创建一个
[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
.
的大小差异正好
我正在尝试为某些 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# 结构的打包吗?
不,打包是一个编译时概念,因为它决定了(除其他外)类型的整体大小。这不能在 运行 时间改变。
在这种情况下,如果您不愿意为 x86 和 x64 分别构建,则必须自己进行编组。感谢@Hans Passant 提供了一种干净的方法来实现这一目标:
- 定义两个结构,比如
SP_DEVINFO_DATA32
和SP_DEVINFO_DATA64
, - 为每个结构定义一个方法重载
- 在 运行 时,select 创建哪个结构并调用适当的重载。
看起来像这样:
[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);
}
}
我在 C++ 和 C# 中完成过,包括 x86 和 x64,我会说 "pack" 对该结构没有任何影响,所以你只需要
[StructLayout(LayoutKind.Sequential)]
在 x86 上,C++ 和 C# 结构都有一个 28 字节的 sizeof
/Marshal.SizeOf
,在 32 字节的 x64 上。如果我创建一个
[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
.