C# 中联合的奇怪解组行为
Strange unmarshalling behavior with union in C#
我想将类似 C 的联合导出到字节数组中,如下所示:
[StructLayout(LayoutKind.Explicit)]
struct my_struct
{
[FieldOffset(0)]
public UInt32 my_uint;
[FieldOffset(0)]
public bool other_field;
}
public static void Main()
{
var test = new my_struct { my_uint = 0xDEADBEEF };
byte[] data = new byte[Marshal.SizeOf(test)];
IntPtr buffer = Marshal.AllocHGlobal(data.Length);
Marshal.StructureToPtr(test, buffer, false);
Marshal.Copy(buffer, data, 0, data.Length);
Marshal.FreeHGlobal(buffer);
foreach (byte b in data)
{
Console.Write("{0:X2} ", b);
}
Console.WriteLine();
}
我们得到的输出 (https://dotnetfiddle.net/gb1wRf) 是 01 00 00 00
而不是预期的 EF BE AD DE
.
现在,如果我们将 other_field
类型更改为 byte
(例如),我们会得到什么?
奇怪的是,我们首先得到了我们想要的输出,EF BE AD DE
(https://dotnetfiddle.net/DnXyMP)
此外,如果我们交换原来的两个字段,我们再次得到我们想要的相同输出 (https://dotnetfiddle.net/ziSQ5W)
为什么会这样?为什么字段的顺序很重要?做同样的事情是否有更好(可靠)的解决方案?
我承认我没有权威的答案来解释为什么 Marshal.StructureToPtr()
会这样,除了清楚它不仅仅是复制字节之外。相反,它必须解释 struct
本身,通过解释该字段的正常规则将每个字段单独编组到目的地。由于 bool
被定义为仅是两个值之一,非零值被映射到 true
,它将原始字节编组为 0x00000001
.
请注意,如果您真的只想要 struct
值中的原始字节,您可以自己进行复制,而不是通过 Marshal
class。例如:
var test = new my_struct { my_uint = 0xDEADBEEF };
byte[] data = new byte[Marshal.SizeOf(test)];
unsafe
{
byte* pb = (byte*)&test;
for (int i = 0; i < data.Length; i++)
{
data[i] = pb[i];
}
}
Console.WriteLine(string.Join(" ", data.Select(b => b.ToString("X2"))));
当然,要使其正常工作,您需要为您的项目启用 unsafe
代码。您可以为有问题的项目执行此操作,也可以将上述内容构建到单独的帮助程序程序集中,其中 unsafe
风险较小(即您不介意为其他代码启用它,and/or 不要不关心程序集是否可验证等)。
这是结构编组方式不可避免的副作用。起点是结构值不可 blittable,这是它包含 bool 的副作用。它在托管结构中占用 1 个字节的存储空间,但在编组结构中占用 4 个字节 (UnmanagedType.Bool)。
所以结构值不能一口气复制,编组器需要转换每个单独的成员。所以 my_uint
是第一个,产生 4 个字节。接下来是 other_field
,也在完全相同的地址处产生 4 个字节。这会覆盖 my_uint
生成的所有内容。
bool 类型通常是一个奇怪的类型,它 never 产生一个 blittable 结构。即使你申请 [MarshalAs(UnmanagedType.U1)]
也不行。这本身对您的测试有一个有趣的影响,您现在会看到 my_int
产生的 3 个高位字节被保留了下来。但是结果仍然是垃圾,因为成员仍然被一个一个地转换,现在在偏移量 0 处产生一个值为 0x01 的字节。
您可以通过将其声明为 byte 来轻松获得您想要的内容,现在结构是 blittable:
[StructLayout(LayoutKind.Explicit)]
struct my_struct {
[FieldOffset(0)]
public UInt32 my_uint;
[FieldOffset(0)]
private byte _other_field;
public bool other_field {
get { return _other_field != 0; }
set { _other_field = (byte)(value ? 1 : 0); }
}
}
我想将类似 C 的联合导出到字节数组中,如下所示:
[StructLayout(LayoutKind.Explicit)]
struct my_struct
{
[FieldOffset(0)]
public UInt32 my_uint;
[FieldOffset(0)]
public bool other_field;
}
public static void Main()
{
var test = new my_struct { my_uint = 0xDEADBEEF };
byte[] data = new byte[Marshal.SizeOf(test)];
IntPtr buffer = Marshal.AllocHGlobal(data.Length);
Marshal.StructureToPtr(test, buffer, false);
Marshal.Copy(buffer, data, 0, data.Length);
Marshal.FreeHGlobal(buffer);
foreach (byte b in data)
{
Console.Write("{0:X2} ", b);
}
Console.WriteLine();
}
我们得到的输出 (https://dotnetfiddle.net/gb1wRf) 是 01 00 00 00
而不是预期的 EF BE AD DE
.
现在,如果我们将 other_field
类型更改为 byte
(例如),我们会得到什么?
奇怪的是,我们首先得到了我们想要的输出,EF BE AD DE
(https://dotnetfiddle.net/DnXyMP)
此外,如果我们交换原来的两个字段,我们再次得到我们想要的相同输出 (https://dotnetfiddle.net/ziSQ5W)
为什么会这样?为什么字段的顺序很重要?做同样的事情是否有更好(可靠)的解决方案?
我承认我没有权威的答案来解释为什么 Marshal.StructureToPtr()
会这样,除了清楚它不仅仅是复制字节之外。相反,它必须解释 struct
本身,通过解释该字段的正常规则将每个字段单独编组到目的地。由于 bool
被定义为仅是两个值之一,非零值被映射到 true
,它将原始字节编组为 0x00000001
.
请注意,如果您真的只想要 struct
值中的原始字节,您可以自己进行复制,而不是通过 Marshal
class。例如:
var test = new my_struct { my_uint = 0xDEADBEEF };
byte[] data = new byte[Marshal.SizeOf(test)];
unsafe
{
byte* pb = (byte*)&test;
for (int i = 0; i < data.Length; i++)
{
data[i] = pb[i];
}
}
Console.WriteLine(string.Join(" ", data.Select(b => b.ToString("X2"))));
当然,要使其正常工作,您需要为您的项目启用 unsafe
代码。您可以为有问题的项目执行此操作,也可以将上述内容构建到单独的帮助程序程序集中,其中 unsafe
风险较小(即您不介意为其他代码启用它,and/or 不要不关心程序集是否可验证等)。
这是结构编组方式不可避免的副作用。起点是结构值不可 blittable,这是它包含 bool 的副作用。它在托管结构中占用 1 个字节的存储空间,但在编组结构中占用 4 个字节 (UnmanagedType.Bool)。
所以结构值不能一口气复制,编组器需要转换每个单独的成员。所以 my_uint
是第一个,产生 4 个字节。接下来是 other_field
,也在完全相同的地址处产生 4 个字节。这会覆盖 my_uint
生成的所有内容。
bool 类型通常是一个奇怪的类型,它 never 产生一个 blittable 结构。即使你申请 [MarshalAs(UnmanagedType.U1)]
也不行。这本身对您的测试有一个有趣的影响,您现在会看到 my_int
产生的 3 个高位字节被保留了下来。但是结果仍然是垃圾,因为成员仍然被一个一个地转换,现在在偏移量 0 处产生一个值为 0x01 的字节。
您可以通过将其声明为 byte 来轻松获得您想要的内容,现在结构是 blittable:
[StructLayout(LayoutKind.Explicit)]
struct my_struct {
[FieldOffset(0)]
public UInt32 my_uint;
[FieldOffset(0)]
private byte _other_field;
public bool other_field {
get { return _other_field != 0; }
set { _other_field = (byte)(value ? 1 : 0); }
}
}