Marshalling a big-endian byte collection into a struct in order to pull out values
Solution 1:
Here's another solution for swapping endianness.
It's adjusted from Adam Robinsons solution here: https://stackoverflow.com/a/2624377/1254743
It's even capable of handling nested structs.
public static class FooTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Foo2
{
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 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;
public Foo2 foo2;
}
public static void test()
{
Foo2 sample2 = new Foo2()
{
b1 = 0x01,
s = 0x0203,
S = 0x0405,
i = 0x06070809,
I = 0x0a0b0c0d,
l = 0xe0f101112131415,
L = 0x161718191a1b1c,
f = 1.234f,
d = 4.56789,
MyString = @"123456789", // null terminated => only 9 characters!
};
Foo sample = new Foo()
{
b1 = 0x01,
s = 0x0203,
S = 0x0405,
i = 0x06070809,
I = 0x0a0b0c0d,
l = 0xe0f101112131415,
L = 0x161718191a1b1c,
f = 1.234f,
d = 4.56789,
MyString = @"123456789", // null terminated => only 9 characters!
foo2 = sample2,
};
var bytes_LE = Dummy.StructToBytes(sample, Endianness.LittleEndian);
var restoredLEAsLE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.LittleEndian);
var restoredLEAsBE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.BigEndian);
var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian);
var restoredBEAsLE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.LittleEndian);
var restoredBEAsBE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.BigEndian);
Debug.Assert(sample.Equals(restoredLEAsLE));
Debug.Assert(sample.Equals(restoredBEAsBE));
Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE));
}
public enum Endianness
{
BigEndian,
LittleEndian
}
private static void MaybeAdjustEndianness(Type type, byte[] data, Endianness endianness, int startOffset = 0)
{
if ((BitConverter.IsLittleEndian) == (endianness == Endianness.LittleEndian))
{
// nothing to change => return
return;
}
foreach (var field in type.GetFields())
{
var fieldType = field.FieldType;
if (field.IsStatic)
// don't process static fields
continue;
if (fieldType == typeof(string))
// don't swap bytes for strings
continue;
var offset = Marshal.OffsetOf(type, field.Name).ToInt32();
// handle enums
if (fieldType.IsEnum)
fieldType = Enum.GetUnderlyingType(fieldType);
// check for sub-fields to recurse if necessary
var subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray();
var effectiveOffset = startOffset + offset;
if (subFields.Length == 0)
{
Array.Reverse(data, effectiveOffset, Marshal.SizeOf(fieldType));
}
else
{
// recurse
MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset);
}
}
}
internal static T BytesToStruct<T>(byte[] rawData, Endianness endianness) where T : struct
{
T result = default(T);
MaybeAdjustEndianness(typeof(T), rawData, endianness);
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
}
finally
{
handle.Free();
}
return result;
}
internal static byte[] StructToBytes<T>(T data, Endianness endianness) where T : struct
{
byte[] rawData = new byte[Marshal.SizeOf(data)];
GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
try
{
IntPtr rawDataPtr = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(data, rawDataPtr, false);
}
finally
{
handle.Free();
}
MaybeAdjustEndianness(typeof(T), rawData, endianness);
return rawData;
}
}
Solution 2:
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;
}