2016-10-20 4 views
1

Я играю с DynamicMethod и стремиться сделать следующее:Создание DynamicMethod от действий <T> инструкции

У меня есть Action, от которого я получить код IL, как байты с помощью GetILAsByteArray(). Из этих байтов я хотел бы создать динамический метод и выполнить. Вот пример того, что я пытаюсь сделать:

class Program 
{ 
    static void Main(string[] args) 
    { 
     //Create action and execute 
     Action<string> myAction = s => 
     { 
      Console.WriteLine("Hello " + s); 
     }; 
     myAction("World"); 
     //Get IL bytes 
     byte[] ilBytes = myAction.GetMethodInfo().GetMethodBody().GetILAsByteArray(); 
     DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) }); 
     DynamicILInfo dynamicIlInfo = dynamicCallback.GetDynamicILInfo(); 
     dynamicIlInfo.SetCode(ilBytes, 100); 
     dynamicCallback.Invoke(null, new object[] { "World" }); 
    } 
} 

При вызове dynamicCallback.Invoke(null, new object[] { "World" }) мы получаем «Exception брошенного:„System.BadImageFormatException“в mscorlib.dll».

Одна вещь, о которой я понятия не имею, - это то, что я должен использовать в качестве второго аргумента для SetCode(), что следует использовать как «maxStackSize»? Как установить то же значение, что и для начального действия? Но я полагаю, что это не причина исключения.

Как правильно создать динамический метод из байтов IL?


Решение

Здесь я хотел бы суммировать полное решение, представленное Дуди Келети:

static void Main(string[] args) 
{ 
    Action<string> myAction = s => 
    { 
     Console.WriteLine("Hello " + s); 
    }; 
    MethodInfo method = myAction.GetMethodInfo(); 
    object target = myAction.Target; 

    DynamicMethod dm = new DynamicMethod(
     method.Name, 
     method.ReturnType, 
     new[] {method.DeclaringType}. 
      Concat(method.GetParameters(). 
       Select(pi => pi.ParameterType)).ToArray(), 
     method.DeclaringType, 
     skipVisibility: true); 

    DynamicILInfo ilInfo = dm.GetDynamicILInfo(); 
    var body = method.GetMethodBody(); 
    SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper(); 
    foreach (LocalVariableInfo lvi in body.LocalVariables) 
    { 
     sig.AddArgument(lvi.LocalType, lvi.IsPinned); 
    } 
    ilInfo.SetLocalSignature(sig.GetSignature()); 
    byte[] code = body.GetILAsByteArray(); 
    ILReader reader = new ILReader(method); 
    DynamicMethodHelper.ILInfoGetTokenVisitor visitor = new DynamicMethodHelper.ILInfoGetTokenVisitor(ilInfo, code); 
    reader.Accept(visitor); 
    ilInfo.SetCode(code, body.MaxStackSize); 

    dm.Invoke(target, new object[] { target, "World" }); 

    Console.ReadLine(); //Just to see the result 
} 

Примечание: DynamicMethodHelper является класс, разработанный Хайбо Л и описал в blog post но также можно загрузить напрямую here.

+0

Я думаю, что вы не можете получить значение maxStackSize, используя отражение. Но на самом деле это не проблема. Проблема заключается в том, что вызов Console.WriteLine кодируется как токен метаданных (вероятно, MethodRef), а токены метаданных действительны только в области модуля, объявляющего его. Посмотрите на функции DynamicILInfo.GetTokenFor, они будут импортировать другие элементы метаданных и создавать токены, действующие для 'DynamicMethod'. – thehennyy

+0

@thehennyy Я пробовал без успеха, вижу мое редактирование. – Sjoerd222888

+0

Вы должны заменить старый токен в массиве байтов IL новым созданным, который возвращает метод GetTokenFor'. – thehennyy

ответ

1

Вы можете сделать это следующим образом:

byte[] code = body.GetILAsByteArray(); 
ILReader reader = new ILReader(method); 
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code); 
reader.Accept(visitor); 
ilInfo.SetCode(code, body.MaxStackSize); 

ILReader является классом, делать тяжелую работу за вас. Вы можете скопировать его с here.

Пример:

MethodInfo method = ... 
DynamicMethod dm = new DynamicMethod(
    method.Name, 
    method.ReturnType, 
    method.GetParameters.Select(pi => pi.ParameterType).ToArray(), 
    method.DeclaringType, 
    skipVisibility: true\fasle - depends of your need); 

DynamicILInfo ilInfo = dm.GetDynamicILInfo(); 
var body = method.GetMethodBody(); 
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper(); 
foreach(LocalVariableInfo lvi in body.LocalVariables) 
{ 
    sig.AddArgument(lvi.LocalType, lvi.IsPinned); 
} 
ilInfo.SetLocalSignature(sig.GetSignature()); 
byte[] code = body.GetILAsByteArray(); 
ILReader reader = new ILReader(method); 
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code); 
reader.Accept(visitor); 
ilInfo.SetCode(code, body.MaxStackSize); 

Если ваш метод является простым методом (не родовое и без исключения ручки), thid должен работать.

Если ваш метод является родовым один, что вам нужно сделать это для передачи тип владельца конструктору DynamicMethod:

var owner = method.DeclaringType.MakeGenericType(
      method.DeclaringType.GetGenericArguments()); 

еще одна вещь, если ее еще не работает, и ваш метод является экземпляром метод, передайте instacne тип метода в первой ячейке массива параметров конструктора DynamicMethod.

Update

Вы не можете пройти null здесь dm.Invoke(**null**, new object[] { "World" }); потому myAction не статический метод.

myAction (Action<string>) - фактически метод в новом сгенерированном классе, который поддерживает этот метод.

Но я проверил, и исключение бросает, даже если я передаю myAction.Target или новый экземпляр этого типа. Исключение (CLR выделяет недействительную программу) сообщает вам, что IL не совсем корректен.Я не могу сейчас точно сказать, в чем проблема, но если это важно для вас, я могу проверить это на следующей неделе, когда вернусь к работе.

В любом случае, если вы просто хотите, чтобы увидеть DynamicIlInfo.SetCode в действии вы можете использовать свой код, как это, но просто изменить данные метод от этого:

class Program 
{   
    static void Main(string[] args) 
    { 
     Action<string> myAction = s => 
     { 
      Console.WriteLine("Hello " + s); 
     }; 
     MethodInfo method = myAction.GetMethodInfo(); 

     //Rest of your code 
    } 
} 

к этому:

class Program 
{ 
    static void M(string s) 
    { 
     Console.WriteLine("Hello " + s); 
    } 

    static void Main(string[] args) 
    { 
     MethodInfo method = typeof (Program).GetMethod("M", BindingFlags.Static | BindingFlags.NonPublic); 

     //Rest of your code 
    } 
} 

Обновление 2:

По-видимому, я очень устал вчера, я не осознавал вашу ошибку.

Как я уже писал в своем первоначальном ответе,

Еще одна вещь, если ее еще не работает, и ваш метод является методом экземпляра, передать тип instacne метода в первой ячейке paramters массив конструктора DynamicMethod.

Так что вам нужно сделать, это:

DynamicMethod dm = new DynamicMethod(
    method.Name, 
    method.ReturnType, 
    new[] {method.DeclaringType}. 
     Concat(method.GetParameters(). 
     Select(pi => pi.ParameterType)).ToArray(), 
    method.DeclaringType, 
    skipVisibility: true); 

и вызывать динамический метод как это:

dm.Invoke(myAction.Target, new object[] { myAction.Target, "World" }); 

Теперь работа совершенна.

+0

Где объявляется '' ILInfoGetTokenVisitor'? – Sjoerd222888

+0

@ Sjoerd222888 [Здесь.] (Https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/01/02/35/ 08/DynamicMethodHelper.cs) И есть сообщение в блоге об этом [здесь] (https://blogs.msdn.microsoft.com/haibo_luo/2006/11/07/turn-methodinfo-to-dynamicmethod/) –

+0

Я сразу же получите сообщение «System.Reflection.TargetInvocationException» с сообщением: «{« Плохая двоичная подпись. (Исключение из HRESULT: 0x80131192) "}" при попытке вызвать динамический метод. Наверное, я пропустил нечто фундаментальное. – Sjoerd222888