繁体   English   中英

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

[英]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 DEhttps://dotnetfiddle.net/DnXyMP

此外,如果我们交换原来的两个字段,我们再次得到我们想要的相同输出( https://dotnetfiddle.net/ziSQ5W

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

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

因此,结构值不能一下子被复制,编组人员需要转换每个成员。 所以my_uint是第一个,产生4个字节。 other_field是下一个,也在完全相同的地址产生4个字节。 这会覆盖my_uint生成的所有内容。

bool类型一般是奇怪的,它永远不会产生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); }
        }
    }

我承认我没有权威的答案为什么Marshal.StructureToPtr()以这种方式运行, Marshal.StructureToPtr() ,它不仅仅是复制字节。 相反,它必须解释struct本身,通过解释该字段的常规规则将每个字段单独编组到目的地。 由于bool被定义为只是两个值中的一个,因此非零值被映射为true ,其将原始字节封送为0x00000001

请注意,如果您真的只想要来自struct值的原始字节,您可以自己进行复制,而不是通过Marshal类。 例如:

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风险较低(即,您不介意将其用于其他代码,和/或不关心程序集正在可验证的等)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM