IL Emit 结构序列化器

IL Emit struct serializer

我正在编写将任何结构编组为字节数组的代码。我有一个方法:

    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;
    }

这是此代码的 IL:

.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

现在我正在尝试发出通用方法:

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[]>));
    }
}

但是当我尝试调用它时失败并显示 CLR detected an invalid program。我认为问题出在这一行:

        generator.Emit(OpCodes.Ldarga_S, parameterName);

但如果我写:

        generator.Emit(OpCodes.Ldarga_S, 0);

如果失败 NullReferenceException


现在我有一段代码可以使用泛型 T 但它使用了未记录的关键字

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;
}

它不稳定,可能会在新的 .Net 版本中崩溃,所以我想用基于 Emit 的代码替换它

有一些问题。首先,您将 parameterName 用于 Ldarga_S 是不正确的,应该是 (byte)0。请注意,需要 byte 强制转换以确保您调用正确的 Emit() 重载 - Ldarga_S 采用 byte 参数,但如果没有强制转换,您将调用重载使用 int 作为它的第二个参数,这在这个例子中实际上并不是一个主要问题,尽管你会在你的输出 IL 中得到 nops - 你的 Ldc_I4_S 也存在类似的问题,它采用 sbyte 参数。

您的局部变量也被错误定义 - 您将它们定义为 typeof(byte) 而它们应该是 typeof(byte[]).

纠正这些问题后,它似乎可以按预期工作。

你把事情搞得太复杂了。这部分

    byte[] result = new byte[12]; 
    Marshal.Copy(p, result, 0, result.Length);
    return result;

根本不需要发射。您只需要发出指针操作,这应该是非常简单的指令。仅发射:

Serialize(new IntPtr(&value), restOfArgs);

其中Serialize是从post开始的三行。