Search code examples
.netinteropmonolegacyendianness

Marshalling a big-endian byte collection into a struct in order to pull out values


There is an insightful question about reading a C/C++ data structure in C# from a byte array, but I cannot get the code to work for my collection of big-endian (network byte order) bytes. (EDIT: Note that my real struct has more than just one field.) Is there a way to marshal the bytes into a big-endian version of the structure and then pull out the values in the endianness of the framework (that of the host, which is usually little-endian)?

(Note, reversing the array of bytes will not work - each value's bytes must be reversed, which does not give you the same collection as reversing all of the bytes.)

This should summarize what I'm looking for (LE=LittleEndian, BE=BigEndian):

void Main()
{
    var leBytes = new byte[] {1, 0, 2, 0};
    var beBytes = new byte[] {0, 1, 0, 2};
    Foo fooLe = ByteArrayToStructure<Foo>(leBytes);
    Foo fooBe = ByteArrayToStructureBigEndian<Foo>(beBytes);
    Assert.AreEqual(fooLe, fooBe);
}

[StructLayout(LayoutKind.Explicit, Size=4)]
public struct Foo  {
    [FieldOffset(0)] 
    public ushort firstUshort;
    [FieldOffset(2)] 
    public ushort secondUshort;
}

T ByteArrayToStructure<T>(byte[] bytes) where T: struct 
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
    handle.Free();
    return stuff;
}

T ByteArrayToStructureBigEndian<T>(byte[] bytes) where T: struct 
{
    ???
}

Other helpful links:

Byte of a struct and onto endian concerns

A little more on bytes and endianness (byte order)

Read binary files more efficiently using C#

Unsafe and reading from files

Mono's contribution to the issue

Mastering C# structs


Solution

  • As alluded to in my comment on @weismat's answer, there is an easy way to achieve big-endian structuring. It involves a double-reversal: the original bytes are reversed entirely, then the struct itself is the reversal of the original (big-endian) data format.

    The fooLe and fooBe in Main will have the same values for all fields. (Normally, the little-endian struct and bytes wouldn't be present, of course, but this clearly shows the relationship between the byte orders.)

    NOTE: See updated code including how to get bytes back out of the struct.

    public void Main()
    {
        var beBytes = new byte[] {
            0x80, 
            0x80,0, 
            0x80,0, 
            0x80,0,0,0, 
            0x80,0,0,0,
            0x80,0,0,0,0,0,0,0, 
            0x80,0,0,0,0,0,0,0, 
            0x3F,0X80,0,0, // float of 1 (see http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness)
            0x3F,0xF0,0,0,0,0,0,0, // double of 1
            0,0,0,0x67,0x6E,0x69,0x74,0x73,0x65,0x54 // Testing\0\0\0
        };
        var leBytes = new byte[] {
            0x80, 
            0,0x80,
            0,0x80, 
            0,0,0,0x80,
            0,0,0,0x80, 
            0,0,0,0,0,0,0,0x80, 
            0,0,0,0,0,0,0,0x80, 
            0,0,0x80,0x3F, // float of 1
            0,0,0,0,0,0,0xF0,0x3F, // double of 1
            0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0
        };
        Foo fooLe = ByteArrayToStructure<Foo>(leBytes).Dump("LE");
        FooReversed fooBe = ByteArrayToStructure<FooReversed>(beBytes.Reverse().ToArray()).Dump("BE");  
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Foo  {
        public byte b1;
        public short s;
        public ushort S;
        public int i;
        public uint I;
        public long l;
        public ulong L;
        public float f;
        public double d;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string MyString;
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct FooReversed  {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string MyString;
        public double d;
        public float f;
        public ulong L;
        public long l;
        public uint I;
        public int i;
        public ushort S;
        public short s;
        public byte b1;
    }
    
    T ByteArrayToStructure<T>(byte[] bytes) where T: struct 
    {
        GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
        handle.Free();
        return stuff;
    }