Casting a byte array to a managed structure
Solution 1:
//I have found this at: http://code.cheesydesign.com/?p=572 (I have not tested yet, but // at first sight it will work well.)
/// <summary>
/// Reads in a block from a file and converts it to the struct
/// type specified by the template parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="reader"></param>
/// <returns></returns>
private static T FromBinaryReader<T>(BinaryReader reader)
{
// Read in a byte array
byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));
// Pin the managed memory while, copy it out the data, then unpin it
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return theStructure;
}
Solution 2:
I'd turn the byte array into a memory stream. Then instantiate a binary reader on that stream. And then define helper functions that take a binary reader and parse a single class.
The built in BinaryReader
class always uses little endian.
I'd use classes instead of structs here.
class PacketHeader
{
uint16_t magic;
uint16_t packet_size;
uint32_t unknown1;
uint32_t unknown2;
uint32_t unknown3;
uint32_t unknown4;
uint16_t unknown5;
uint16_t unknown6;
uint32_t unknown7;
uint32_t unknown8;
string packet_type; // replaced with a real string
};
PacketHeader ReadPacketHeader(BinaryReader reader)
{
var result=new PacketHeader();
result.magic = reader.ReadInt16();
...
result.packet_type=ReadCString();//Some helper function you might need to define yourself
return result;
}
Solution 3:
This is how i did:
using System;
using System.Runtime.InteropServices;
public static object GetObjectFromBytes(byte[] buffer, Type objType)
{
object obj = null;
if ((buffer != null) && (buffer.Length > 0))
{
IntPtr ptrObj = IntPtr.Zero;
try
{
int objSize = Marshal.SizeOf(objType);
if (objSize > 0)
{
if (buffer.Length < objSize)
throw new Exception(String.Format("Buffer smaller than needed for creation of object of type {0}", objType));
ptrObj = Marshal.AllocHGlobal(objSize);
if (ptrObj != IntPtr.Zero)
{
Marshal.Copy(buffer, 0, ptrObj, objSize);
obj = Marshal.PtrToStructure(ptrObj, objType);
}
else
throw new Exception(String.Format("Couldn't allocate memory to create object of type {0}", objType));
}
}
finally
{
if (ptrObj != IntPtr.Zero)
Marshal.FreeHGlobal(ptrObj);
}
}
return obj;
}
And in the struct definition I didn't use any fixed
region, instead I used the MarshalAs
attribute if the standard marshalling didn't worked. This is what you will probally need for the string.
You would use this function like this:
PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader));
Edit: I didn´t see your BigEndian "restriction" in the code example. This solution will only work if the bytes are LittleEndian.
Edit 2: In the string of your example you would decorate it with:
[MarshalAs(UnmanagedType.LPStr)]
In the arrays I would go with something like this for a n-sized array:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = n)]
Solution 4:
For those who have access to C# 7.3 features, I use this piece of unsafe code to "serialize" to bytes:
public static class Serializer
{
public static unsafe byte[] Serialize<T>(T value) where T : unmanaged
{
byte[] buffer = new byte[sizeof(T)];
fixed (byte* bufferPtr = buffer)
{
Buffer.MemoryCopy(&value, bufferPtr, sizeof(T), sizeof(T));
}
return buffer;
}
public static unsafe T Deserialize<T>(byte[] buffer) where T : unmanaged
{
T result = new T();
fixed (byte* bufferPtr = buffer)
{
Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T));
}
return result;
}
}
A unmanaged
type can be a struct (simple struct without reference types, those a considered managed structs) or a native type such as int
, short
, etc.
Solution 5:
If you want fast code without copy, this is the solution. We are working here on raw byte[]
, simply casting the pointer in unsafe
code, as you would in native C / C++. So there is no overhead calling into expensive framework methods, making copies etc. etc.
Any change to the unmanaged struct
will reflect in managed byte[]
, and vice versa.
//FOR DEBUG/TEST ONLY
using System.Runtime.InteropServices;
namespace ByteStructCast1
{
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct StructTest//4B
{
[MarshalAs(UnmanagedType.U2)]
public ushort item1; //2B
public fixed byte item2[2]; //2B =2x 1B
}
static void Main(string[] args)
{
//managed byte array
byte[] DB1 = new byte[7]; //7B more than we need. byte buffer usually is greater.
DB1[0] = 2;//test data |> LITTLE ENDIAN
DB1[1] = 0;//test data |
DB1[2] = 3;//test data
DB1[3] = 4;//test data
unsafe //we'll now pin unmanaged struct over managed byte array
{
fixed(byte* db1 = DB1) //db1 is pinned pointer to DB1 byte[] array
{
//StructTest t1 = *(StructTest*)db1; //does not change DB1/db1
//t1.item1 = 11; //does not change DB1/db1
db1[0] = 22; //does CHANGE DB1/db1
DB1[0] = 33; //does CHANGE DB1/db1
StructTest* ptest = (StructTest*)db1; //does CHANGE DB1/db1
ptest->item1 = 44; //does CHANGE DB1/db1
ptest->item2[0]++; //does CHANGE DB1/db1
ptest->item2[1]--; //does CHANGE DB1/db1
}
}
}
}
}
This can also be used when you are working with primitive-typed fixed
-size buffers, and need to work on elements thereof as struct
s with members, e.g. ulong
to MyStruct
, both 64 bits long.