2015-02-25 2 views
1

Я пишу код, который маршалирует любую структуру в байтовый массив. У меня есть метод: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

+0

[Это помогает] (http://stackoverflow.com/a/25311889/2530848)? –

+0

@SriramSakthivel нет, потому что нет проблемы с методом Serialize (а затем с Marshal.Copy), это проблема с его испусканием. –

+0

Я понимаю, но почему вам нужно испускать динамический метод на первом месте? Мой ответ в данной ссылке является общим, вы можете использовать его. –

ответ

2

Есть несколько проблем. Во-первых, ваше использование parameterName для Ldarga_S неверно, оно должно быть (byte)0. Обратите внимание, что приведение byte требуется, чтобы вы вызывали правильную перегрузку Emit(). Ldarga_S принимает параметр byte, но без трансляции вы вызываете перегрузку с int в качестве второго параметра, это не является главной проблемой в этот экземпляр, хотя вы получите nop s в вашем выводе IL - аналогичная проблема присутствует для вашего Ldc_I4_S, который принимает параметр sbyte.

Ваши локальные переменные также определены неверно - вы определяете их как typeof(byte), тогда как они должны быть typeof(byte[]).

Как только они исправлены, он работает как ожидалось.

+0

Большое спасибо, проблема была в самом деле с 'byte' вместо' byte [] 'declaration. Но я не согласен с тем, что 'Ldc_I4_S' с параметром байта, потому что struct может быть больше 256 байтов. –

+1

@AlexZhukovskiy - Я понимаю, что структура может быть большой, однако конкретный короткий код op-code 'Ldc_I4_S' позволяет только загружать значения' int', которые находятся в диапазоне 'sbyte' (а не без знака' byte' as Я изначально предложил - я исправил это в ответе). Если вы хотите поддерживать структуры размером> 127 байт, вместо этого вы должны использовать 'Ldc_I4', что позволяет загрузить любое 32-битное значение' int' или динамически переключаться между короткой и длинной формой инструкции в зависимости от значения. – Iridium

+0

Вы снова правы, я заменил его на 'OpCode loadStructSizeOpCode = sizeInBytes

0

Вы делаете это слишком сложным. Эта часть

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

не нужно испускать вообще. Вам нужно только исправить операцию указателя, которая должна быть очень легким. Выделяют только:

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

Где Serialize это три линии с начала этого поста.