Using .NET 4.0 I can quickly convert a struct to and from an array of bytes by making use of the Marshal class. For example, the following simple example will run at around 1 million times per second on my machine which is fast enough for my purposes...
[StructLayout(LayoutKind.Sequential)]
public struct ExampleStruct
{
int i1;
int i2;
}
public byte[] StructToBytes()
{
ExampleStruct inst = new ExampleStruct();
int len = Marshal.SizeOf(inst);
byte[] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(inst, ptr, true);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
But the Marshal class is not available under WinRT, which is reasonable enough for security reasons, but it means I need another way to achieve my struct to/from byte array.
I am looking for an approach that works for any fixed size struct. I could solve the problem by writing custom code for each struct that knows how to convert that particular struct to and form a byte array but that is rather tedious and I cannot help but feel there is some generic solution.
One approach would be a combination of Expressions and Reflection (I'll leave caching as an implementation detail):
// Action for a given struct that writes each field to a BinaryWriter
static Action<BinaryWriter, T> CreateWriter<T>()
{
// TODO: cache/validate T is a "simple" struct
var bw = Expression.Parameter(typeof(BinaryWriter), "bw");
var obj = Expression.Parameter(typeof(T), "value");
// I could not determine if .Net for Metro had BlockExpression or not
// and if it does not you'll need a shim that returns a dummy value
// to compose with addition or boolean operations
var body = Expression.Block(
from f in typeof(T).GetTypeInfo().DeclaredFields
select Expression.Call(
bw,
"Write",
Type.EmptyTypes, // Not a generic method
new[] { Expression.Field(obj, f.Name) }));
var action = Expression.Lambda<Action<BinaryWriter, T>>(
body,
new[] { bw, obj });
return action.Compile();
}
Used like so:
public static byte[] GetBytes<T>(T value)
{
// TODO: validation and caching as necessary
var writer = CreateWriter(value);
var memory = new MemoryStream();
writer(new BinaryWriter(memory), value);
return memory.ToArray();
}
To read this back, it is a bit more involved:
static MethodInfo[] readers = typeof(BinaryReader).GetTypeInfo()
.DeclaredMethods
.Where(m => m.Name.StartsWith("Read") && !m.GetParameters().Any())
.ToArray();
// Action for a given struct that reads each field from a BinaryReader
static Func<BinaryReader, T> CreateReader<T>()
{
// TODO: cache/validate T is a "simple" struct
var br = Expression.Parameter(typeof(BinaryReader), "br");
var info = typeof(T).GetTypeInfo();
var body = Expression.MemberInit(
Expression.New(typeof(T)),
from f in info.DeclaredFields
select Expression.Bind(
f,
Expression.Call(
br,
readers.Single(m => m.ReturnType == f.FieldType),
Type.EmptyTypes, // Not a generic method
new Expression[0]));
var function = Expression.Lambda<Func<BinaryReader, T>>(
body,
new[] { br });
return function.Compile();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With