Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinRT and persisting struct to and from an array of bytes?

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.

like image 366
Phil Wright Avatar asked Oct 07 '11 02:10

Phil Wright


1 Answers

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();
}
like image 133
user7116 Avatar answered Oct 16 '22 13:10

user7116