Проблема MethodBuilder.CreateMethodBody () в создании динамического типа - PullRequest
4 голосов
/ 16 декабря 2010

Для эксперимента я пытаюсь прочитать тело метода (используя GetILAsByteArray () ) из исходного типа и добавив его в новый тип (используя 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;
    }
}

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

.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 обнаружил недопустимую программу" при вызове метода.

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

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");

Редактировать: И интерфейс ITest (Target)

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

РЕДАКТИРОВАТЬ:

при комментировании этой строки

  //var ilGen = MthdBldr.GetILGenerator();

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

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

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

Anyпомощь очень ценится ....:)

Ответы [ 3 ]

7 голосов
/ 16 декабря 2010

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

Весьма вероятно, что реализация не анализирует байтовый массив IL, поэтому устанавливает максимальный стекдо 16 из синего.

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

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

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

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

3 голосов
/ 16 декабря 2010

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

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, если его нет, так что вы можете застрять.

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

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

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

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

Эта статья CodeProject, Разбор IL тела метода , покажет вам один способ синтаксического анализа IL.Он использует тип OpCodes для построения отображения из кода в структуру OpCode.Вы можете прочитать инструкции по одному и использовать OperandType , чтобы определить, как читать и переводить аргумент.

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

0 голосов
/ 16 декабря 2010

Вам нужно переопределить аргументы в стеке, чтобы иметь возможность работать с ним.Код копирования байтов 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...