Создать DynamicMethod из инструкции Action<T>

Я играю с DynamicMethod и стремлюсь сделать следующее:

У меня есть действие, из которого я получаю код 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" }) мы получаем "Исключение:" System.BadImageFormatException "в mscorlib.dll".

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

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


Решение

Здесь я хотел бы обобщить полное решение, предоставленное Dudi Keleti:

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

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 это класс, который делает тяжелую работу за вас. Вы можете скопировать его отсюда.

Пример:

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 в первую ячейку массива paramters DynamicMethod конструктор.

Обновить

Вы не можете пройти 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" });

Теперь это отлично работает.

Другие вопросы по тегам