C#中使用union的奇怪的解组行为

mic*_*el9 3 c# boolean unmarshalling unions

我想将类似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();
}
Run Code Online (Sandbox Code Playgroud)

我们获得的输出(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)

为什么会这样?为什么字段的顺序很重要?做同样的事情是否有更好(可靠)的解决方案?

Han*_*ant 5

这是结构编组方式不可避免的副作用.起点是结构值不是blittable,它的副作用包含bool.这在托管结构中占用1个字节,而在封送结构中占用4个字节(UnmanagedType.Bool).

因此,结构值不能一下子被复制,编组人员需要转换每个成员.所以my_uint首先是产生4个字节.的other_field是下一个,也产生在准确的同一地址的4个字节.这会覆盖所有my_uint产生的东西.

布尔类型是一般的古怪,它从未产生blittable结构.申请时甚至都没有[MarshalAs(UnmanagedType.U1)].这本身对您的测试有一个有趣的影响,您现在将看到my_int保留的3个高位字节.但结果仍然是垃圾,因为成员仍然是逐个转换的,现在在偏移0处产生值为0x01的单个字节.

您可以通过将其声明为字节来轻松获得所需内容,现在结构是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); }
        }
    }
Run Code Online (Sandbox Code Playgroud)