简体   繁体   中英

Strange unmarshalling behavior with union in C#

I want to export a C-like union into a byte array, like this :

[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 .

Now, what do we get if we change the other_field type to byte (for instance) ?

Oddly, we get the output we wanted in the first place, EF BE AD DE ( https://dotnetfiddle.net/DnXyMP )

Moreover, if we swap the original two fields, we again get the same output we wanted ( 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 . Which takes 1 byte of storage in the managed struct but 4 bytes in the marshaled struct (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. The other_field is next, also producing 4 bytes at the exact same address. Which overwrites everything that my_uint produced.

The bool type is an oddity in general, it never produces a blittable struct. Not even when you apply [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. 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.

You can easily get what you want by declaring it as a byte instead, now the struct is 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. Rather, it must be interpreting the struct itself, marshaling each field individually to the destination via the normal rules for interpreting that field. 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 .

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. 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. 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.).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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