【问题标题】:IL Emit struct serializerIL Emit 结构序列化器
【发布时间】:2015-02-25 19:34:38
【问题描述】:

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

    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 的代码替换它

【问题讨论】:

  • @SriramSakthivel 不,因为方法 Serialize(然后是 Marshal.Copy)没有问题,发出它的问题。
  • 我明白了,但是为什么首先需要发出动态方法?我在给定链接中的答案是通用的,您可以使用它。
  • 因为我无法在 C# 中获取未知泛型类型 T 的地址,所以可以使用 IL 来完成。见编辑
  • 当我给你一个没有不安全代码的解决方案时,我不明白为什么你需要发出一个方法(这也是不安全的)?你的目标是什么?将任何结构转换为 byte[] 对吗?我的代码正是这样做的。

标签: c# .net .net-4.5 code-generation reflection.emit


【解决方案1】:

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

您的局部变量也定义不正确 - 您将它们定义为 typeof(byte) 而它们应该是 typeof(byte[])

一旦这些被纠正,它似乎可以按预期工作。

【讨论】:

  • 非常感谢,问题出在byte 而不是byte[] 声明。但我不同意 Ldc_I4_S 带有 byte 参数,因为 struct 可能大于 256 字节。
  • @AlexZhukovskiy - 我知道结构可能很大,但是特定的短格式操作码Ldc_I4_S 只允许加载int 范围内的intsbyte (不是我最初建议的未签名的byte - 我已在答案中更正了这一点)。如果您希望支持 >127 字节的结构,则必须改用 Ldc_I4,它允许加载任何 32 位 int 值,或者根据值在指令的短格式和长格式之间动态切换。
  • 你又是对的,我把它换成了OpCode loadStructSizeOpCode = sizeInBytes &lt; sbyte.MaxValue ? OpCodes.Ldc_I4_S : OpCodes.Ldc_I4
【解决方案2】:

你把这弄得太复杂了。这部分

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

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

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

Serialize 是本文开头的三行代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多