2010-12-16 1 views
4

Для эксперимента я пытаюсь прочитать тело методы (с использованием GetILAsByteArray()) от типа источника и добавить его к новому типу (с помощью CreateMethodBody()).MethodBuilder.CreateMethodBody() проблема динамического создания Типа

Мой класс источник просто это

public class FullClass 
{ 
    public string Test(string data) 
    { 
     return data; 
    } 
    public string Test2(string data) 
    { 
     return data; 
    } 
    public string Test5(string data, string data1) 
    { 
     return data + data1; 
    } 
} 

Ил генерируется для этого кода (взятый с помощью отражателя)

.method public hidebysig instance string Test(string data) cil managed 
{ 
    .maxstack 1 
    .locals init (
     [0] string CS$1$0000) 
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005 
    L_0005: ldloc.0 
    L_0006: ret 
} 

Но IL генерируется из моего нового типа выглядит следующим образом

.method public hidebysig virtual instance string Test(string) cil managed 
{ 
    .maxstack 0 
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: stloc.0 
    L_0003: br.s L_0005 
    L_0005: ldloc.0 
    L_0006: ret 
} 

Различия в значении maxstack &. Директивы. Я не понимаю, почему мой фактический класс генерирует locals, хотя он не имеет каких-либо локальных переменных?

И почему различия в значении .maxstack, так как я использую тот же IL из источника, чтобы создать новый тип.?

Из-за этого я получаю сообщение об ошибке «Common Language Runtime обнаружил недопустимую программу» при вызове метода.

Мой код создания динамического типа выглядит следующим образом

public static class Mixin<Target> 
    { 

     public static Target compose<TSource>() 
     { 
      Type newType = null; 

      AppDomain currentDom = Thread.GetDomain(); 

      AssemblyName DAssembly = new AssemblyName(); 
      DAssembly.Name = "DynamicTypesAssembly"; 

      AssemblyBuilder DAssemblyBldr = currentDom.DefineDynamicAssembly(
           DAssembly, 
           AssemblyBuilderAccess.RunAndSave); 



      ModuleBuilder DModuleBldr = DAssemblyBldr.DefineDynamicModule(DAssembly.Name, DAssembly.Name + ".dll", false); 
     // var DInterface = EmitInterface(DModuleBldr); 
      TypeBuilder TypeBldr = DModuleBldr.DefineType("WorkOut.DType", 
        TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable 
        ,typeof(object), new[] { typeof(Target) }); 

      //TypeBldr.AddInterfaceImplementation(typeof(DInterface)); 

      var methodCol = typeof(Target).GetMethods(BindingFlags.Public| BindingFlags.Instance); 

      foreach (var ms in methodCol) 
      { 
       var paramCol = ms.GetParameters(); 
       var paramTypeArray = paramCol.Select(x => x.ParameterType).ToArray(); 
       var paramNameArray = paramCol.Select(x=>x.Name).ToArray(); 
       MethodBuilder MthdBldr = TypeBldr.DefineMethod(ms.Name, 
            MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, 
            ms.ReturnType, 
            paramTypeArray); 

       for(int i=0;i<paramCol.Count();i++) 
       { 
        MthdBldr.DefineParameter(i+1, ParameterAttributes.None, paramNameArray[i]); 
       } 


       MethodInfo[] methodInfos = typeof(TSource).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | 
          BindingFlags.Static | BindingFlags.Instance); 

       for (int i = 0; i < methodInfos.Count(); i++) 
       { 
        var paramSrc = methodInfos[i].GetParameters(); 
        var paramSrcTypeArray = paramSrc.Select(x => x.ParameterType).ToArray(); 

        if (methodInfos[i].Name == ms.Name && methodInfos[i].ReturnType == ms.ReturnType && paramSrc.Count() == paramCol.Count() && paramTypeArray.SequenceEqual(paramSrcTypeArray)) 
        { 
         var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray(); 
         var ilGen = MthdBldr.GetILGenerator(); 
         //ilGen.Emit(OpCodes.Ldarg_0); //Load the 'this' reference onto the evaluation stack 
         //ilGen.Emit(OpCodes.Initobj); 
         MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length); 
         //ilGen.Emit(OpCodes.Ret); 
         break; 
        } 
       } 

      } 
      newType = TypeBldr.CreateType(); 
      DAssemblyBldr.Save("a.dll"); 
      return (Target)Activator.CreateInstance(newType); 
     } 

и код для вызова этого

 var resMix = Mixin<ITest>.compose<FullClass>(); 
    var returned1 = resMix.Test("sam"); 

Edit: И интерфейс ITest (Target) является

public interface ITest 
{ 
    string Test(string data);  
} 

EDIT:

, комментируя эту линию

//var ilGen = MthdBldr.GetILGenerator(); 

maxstack становится .maxstack 16

Я провел проверку против новой библиотеки DLL против PEverify инструмент, это дает следующие ошибки

WorkOut.DType :: Test] [offset 0x00000002] Нераспознанный номер локальной переменной.

Любая помощь действительно оценили .... :)

+0

Привет, Рамеш, можете ли вы запросить дополнительную информацию или принять ответ? – 2011-01-07 11:05:12

+0

@Jb, я использую ваш читатель IL только ... забыл обновить .. изменит его .. – RameshVel 2011-01-07 11:39:28

ответ

7

Как страница MSDN о CreateMethodBody говорит, это не поддерживается полностью.

Очень вероятно, что реализация не анализирует IL-массив байтов, поэтому он устанавливает maxstack на 16 из синего.

Если вы создаете ILGenerator для метода, он установит метод maxstack равным нулю. ILGenerator будет увеличивать его, когда вы будете использовать различные перегрузки Emit. Поскольку вы этого не делаете и используете CreateMethodBody, он остается нулевым. Это объясняет разницу.

CreateMethodBody определенно проблематичен для сценариев, которые включают в себя что-либо, кроме простого кода. Каждый код операции, который принимает токен метаданных, не будет использоваться, так как вы не знаете конечный токен в области модуля при создании массива байтов. И это не позволяет вам выделять обработчики исключений.

Короче говоря, CreateMethodBody как есть, бессмысленно.

Если вы хотите продолжить эксперименты, я предлагаю вам использовать мой reflection IL reader, чтобы получить представление инструкции методов, а затем использовать ILGenerator для воспроизведения тела метода внутри конструктора методов.

0

Вы должны переобъявить арг на стеке, чтобы иметь возможность работать с ним.Байты IL скопировать код должен быть такой:

var ILcodes = methodInfos[i].GetMethodBody().GetILAsByteArray(); 
ILGenerator ILGen = MthdBldr.GetILGenerator(); 
foreach (ParameterInfo parameter in paramSrc) 
{ 
    ILGen.DeclareLocal(parameter.ParameterType); 
} 
MthdBldr.CreateMethodBody(ILcodes, ILcodes.Length); 
+0

благодарит за информацию. Теперь я получаю .locals. Но все равно .maxstack равно 0. И получение ошибки «[offset 0x00000001] Stack overflow» в PEverify.exe. :( – RameshVel 2010-12-16 14:14:11

+0

Это ответ на ваш вопрос. Если вам нужно более общее решение, вам придется переписать весь тело функции, инструкцию по инструкции, и вам понадобится более полное переопределение IL в целом :-) – 2010-12-16 19:51:28

3

Ну, вы можете получить за ошибки «непризнанного локального переменного числа» делать что-то вроде этого:

var ilGen = MthdBldr.GetILGenerator(); 
foreach (var localVariable in methodInfos[i].GetMethodBody().LocalVariables) 
{ 
    ilGen.DeclareLocal(localVariable.LocalType, localVariable.IsPinned); 
} 

я могу реально запустить программу в .NET 3.5/VS2008, хотя он все еще сбой в .NET 4.0/VS2010, вероятно, потому, что maxstack не прав. Если вы посмотрите на TypeBuilder.CreateTypeNoLock в Reflector, он вытащит maxStackSize из ilGenerator, если он есть, и использует 16, если таковых нет, поэтому вы можете застрять.

Большая проблема, с которой вы столкнетесь, заключается в том, что вы копируете metadata tokens байт за байт. От MSDN:

Идентификаторы метаданных определены в пределах области . Например, токен метаданных со значением N полностью идентифицирует, в пределах данной области, запись, которая содержит сведения о типе . Однако в другой области токен метаданных с тем же значением N может указывать полную запись .

Как только вы обрабатываете метод, который считывает поле или вызывает другой метод, вы собираетесь получить загадочную ошибку, как «MissingFieldException: Поле не найдено:„ WorkOut.DType“.»

Если вы действительно хотите скопировать метод, вам нужно разобрать IL, использовать API-интерфейсы Reflection на Module такие как Module.ResolveMember преобразовать маркера метаданных к объектам MemberInfo, а затем использовать ILGenerator.Emit перегрузок для преобразования тех новых метаданных токенов в вашей динамической сборке.

Эта статья CodeProject, Parsing the IL of a Method Body, покажет вам один способ разбора ИЛ. Он использует тип OpCodes для построения отображения из кода в структуру OpCode. Вы можете прочитать инструкции один за другим и использовать OperandType, чтобы определить, как читать и переводить аргумент.

(Обратите внимание, что я не рекомендовал бы делать все это в рабочем коде, но вы говорите «для эксперимента», и это, безусловно, будет интересно.)