Search code examples
c#.net.net-4.5code-generationreflection.emit

IL Emit struct serializer


I'm writing a code that marshal any structure to byte array. I have a method:

    public static byte[] Serialize(MyStruct value)
    {
        IntPtr p = new IntPtr(&value);
        byte[] result = new byte[12]; 
        Marshal.Copy(p, result, 0, result.Length);
        return result;
    }

Here is IL for this code:

.method public hidebysig static uint8[]  Serialize(valuetype Program/MyStruct 'value') cil managed
{
  // code size:       37 (0x25)
  .maxstack  4
  .locals init ([0] native int p,
           [1] uint8[] result,
           [2] uint8[] V_2)
  IL_0000:  nop
  IL_0001:  ldloca.s   p
  IL_0003:  ldarga.s   'value'
  IL_0005:  conv.u
  IL_0006:  call       instance void [mscorlib]System.IntPtr::.ctor(void*)
  IL_000b:  ldc.i4.s   12
  IL_000d:  newarr     [mscorlib]System.Byte
  IL_0012:  stloc.1
  IL_0013:  ldloc.0
  IL_0014:  ldloc.1
  IL_0015:  ldc.i4.0
  IL_0016:  ldloc.1
  IL_0017:  ldlen
  IL_0018:  conv.i4
  IL_0019:  call       void [mscorlib]System.Runtime.InteropServices.Marshal::Copy(native int,
                                                                                   uint8[],
                                                                                   int32,
                                                                                   int32)
  IL_001e:  nop
  IL_001f:  ldloc.1
  IL_0020:  stloc.2
  IL_0021:  br.s       IL_0023
  IL_0023:  ldloc.2
  IL_0024:  ret
} // end of method MyStruct::Serialize

now I'm trying to emit generic method:

private static class SerializationHolder<T> where T : struct
{
    public static readonly Func<T, byte[]> Value = CreateDelegate();

    private static Func<T, byte[]> CreateDelegate()
    {
        var dm = new DynamicMethod("Serialize" + typeof (T).Name,
            typeof (byte[]),
            new[] {typeof (T)},
            Assembly.GetExecutingAssembly().ManifestModule);
        const string parameterName = "value";
        dm.DefineParameter(1, ParameterAttributes.None, parameterName);
        var generator = dm.GetILGenerator();
        var p = generator.DeclareLocal(typeof (IntPtr));
        generator.DeclareLocal(typeof (byte));
        generator.DeclareLocal(typeof (byte));
        generator.Emit(OpCodes.Ldloca_S, p);
        generator.Emit(OpCodes.Ldarga_S, parameterName);
        generator.Emit(OpCodes.Conv_U);

        var intPtrCtor = typeof (IntPtr).GetConstructor(new[] {typeof(void*)});
        Debug.Assert(intPtrCtor != null);
        generator.Emit(OpCodes.Call, intPtrCtor);
        var sizeInBytes = Marshal.SizeOf(typeof (T));
        generator.Emit(OpCodes.Ldc_I4_S, sizeInBytes);
        generator.Emit(OpCodes.Newarr, typeof (byte));
        generator.Emit(OpCodes.Stloc_1);
        generator.Emit(OpCodes.Ldloc_0);
        generator.Emit(OpCodes.Ldloc_1);
        generator.Emit(OpCodes.Ldc_I4_0);
        generator.Emit(OpCodes.Ldloc_1);
        generator.Emit(OpCodes.Ldlen);
        generator.Emit(OpCodes.Conv_I4);

        var marshalCopy = typeof (Marshal).GetMethod("Copy", new[] {typeof (IntPtr), typeof (byte[]), typeof (int), typeof (int)});
        generator.EmitCall(OpCodes.Call, marshalCopy, null);
        generator.Emit(OpCodes.Ldloc_1);
        generator.Emit(OpCodes.Stloc_2);
        generator.Emit(OpCodes.Ldloc_2);
        generator.Emit(OpCodes.Ret);

        return (Func<T, byte[]>)dm.CreateDelegate(typeof(Func<T, byte[]>));
    }
}

but when I'm trying to call it it fails with CLR detected an invalid program. I think the problem is in this line:

        generator.Emit(OpCodes.Ldarga_S, parameterName);

but if I'm writing:

        generator.Emit(OpCodes.Ldarga_S, 0);

if fails with NullReferenceException


Now I have a code that do it with generic type T but it uses undocumented keywords

public static byte[] Serialize<T>(this T value) where T : struct
{
    TypedReference tr = __makeref(value);
    IntPtr p = *(IntPtr*)&tr;
    int sizeInBytes = Marshal.SizeOf(typeof(T));
    byte[] result = new byte[sizeInBytes];
    Marshal.Copy(p, result, 0, result.Length);
    return result;
}

It's unstable and can break down in new .Net releases so I want to replace it with Emit-based code


Solution

  • There are a few problems. Firstly, your use of parameterName for Ldarga_S is incorrect, it should be (byte)0. Note that the byte cast is required to ensure you're calling the correct Emit() overload - Ldarga_S takes a byte parameter, but without the cast you're calling the overload with int as its second parameter, this isn't actually a major issue in this instance though you'll get nops in your output IL - a similar issue is present for your Ldc_I4_S, which takes an sbyte parameter.

    Your local variables are also defined incorrectly - you're defining them as typeof(byte) whereas they should be typeof(byte[]).

    Once these are corrected, it appears to work as expected.