简体   繁体   English

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

[英]Strange unmarshalling behavior with union in C#

I want to export a C-like union into a byte array, like this : 我想将类似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();
}

The output we get ( https://dotnetfiddle.net/gb1wRf ) is 01 00 00 00 instead of the expected EF BE AD DE . 我们获得的输出( https://dotnetfiddle.net/gb1wRf )是01 00 00 00而不是预期的EF BE AD DE

Now, what do we get if we change the other_field type to byte (for instance) ? 现在,如果我们将other_field类型更改为byte (例如),我们会得到什么?

Oddly, we get the output we wanted in the first place, EF BE AD DE ( https://dotnetfiddle.net/DnXyMP ) 奇怪的是,我们首先获得了我们想要的输出, EF BE AD DEhttps://dotnetfiddle.net/DnXyMP

Moreover, if we swap the original two fields, we again get the same output we wanted ( https://dotnetfiddle.net/ziSQ5W ) 此外,如果我们交换原来的两个字段,我们再次得到我们想要的相同输出( https://dotnetfiddle.net/ziSQ5W

Why is this happening? 为什么会这样? Why would the order of the fields matter ? 为什么字段的顺序很重要? Is there a better (reliable) solution for doing the same thing ? 做同样的事情是否有更好(可靠)的解决方案?

This is an inevitable side-effect of the way a structure is marshaled. 这是结构编组方式不可避免的副作用。 Starting point is that the structure value is not blittable, a side-effect of it containing a bool . 起点是结构值不是blittable,它的副作用包含bool Which takes 1 byte of storage in the managed struct but 4 bytes in the marshaled struct (UnmanagedType.Bool). 这在托管结构中占用1个字节,而在封送结构中占用4个字节(UnmanagedType.Bool)。

So the struct value cannot just be copied in one fell swoop, the marshaller needs to convert each individual member. 因此,结构值不能一下子被复制,编组人员需要转换每个成员。 So the my_uint is first, producing 4 bytes. 所以my_uint是第一个,产生4个字节。 The other_field is next, also producing 4 bytes at the exact same address. other_field是下一个,也在完全相同的地址产生4个字节。 Which overwrites everything that my_uint produced. 这会覆盖my_uint生成的所有内容。

The bool type is an oddity in general, it never produces a blittable struct. bool类型一般是奇怪的,它永远不会产生blittable结构。 Not even when you apply [MarshalAs(UnmanagedType.U1)] . 即使你应用[MarshalAs(UnmanagedType.U1)]也不行。 Which in itself has an interesting effect on your test, you'll now see that the 3 upper bytes produced by my_int are preserved. 这本身对您的测试有一个有趣的影响,您现在将看到my_int产生的3个高位字节被保留。 But the result is still junk since the members are still converted one-by-one, now producing a single byte of value 0x01 at offset 0. 但结果仍然是垃圾,因为成员仍然是逐个转换的,现在在偏移0处产生值为0x01的单个字节。

You can easily get what you want by declaring it as a byte instead, now the struct is blittable: 您可以通过将其声明为字节来轻松获得所需内容,现在结构是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); }
        }
    }

I admit that I don't have an authoritative answer for why Marshal.StructureToPtr() behaves this way, other than that clear it is doing more than just copying bytes. 我承认我没有权威的答案为什么Marshal.StructureToPtr()以这种方式运行, Marshal.StructureToPtr() ,它不仅仅是复制字节。 Rather, it must be interpreting the struct itself, marshaling each field individually to the destination via the normal rules for interpreting that field. 相反,它必须解释struct本身,通过解释该字段的常规规则将每个字段单独编组到目的地。 Since bool is defined to only ever be one of two values, the non-zero value gets mapped to true , which marshals to raw bytes as 0x00000001 . 由于bool被定义为只是两个值中的一个,因此非零值被映射为true ,其将原始字节封送为0x00000001

Note that if you really just want the raw bytes from the struct value, you can do the copying yourself instead of going through the Marshal class. 请注意,如果您真的只想要来自struct值的原始字节,您可以自己进行复制,而不是通过Marshal类。 For example: 例如:

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"))));

Of course, for that to work you will need to enable unsafe code for your project. 当然,要实现这一点,您需要为项目启用unsafe代码。 You can either do that for the project in question, or build the above into a separate helper assembly where unsafe is less risky (ie where you don't mind enabling it for other code, and/or don't care about the assembly being verifiable, etc.). 您可以为相关项目执行此操作,或者将上述内容构建到单独的帮助程序集中,其中unsafe风险较低(即,您不介意将其用于其他代码,和/或不关心程序集正在可验证的等)。

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

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