Как создать DynamicMethod из IL-кода?

136
24 апреля 2021, 20:00

мне необходимо создать DynamicMethod по IL-коду следующего метода:

public int test(string v1, int v2)
{
    return (int)call(new object[]{ v1, v2 });
}

где

public object call(object[] args)
{
    // что-то возвращающее int
}

Я переписал код из ildasm в ILGenerator.Emit, но при выполнении сгенерированного делегата появляется ошибка недопустимого кода MSIL. Помогите, пожалуйста, создать корректные вызовы ILGenerator.Emit для генерации метода, либо пришлите ссылки на литературу по этому. Заранее благодарю!

Type[] arg_types = new Type[] { typeof(string), typeof(int) };
            var dn = new DynamicMethod("myMethod", typeof(int), arg_types);
            var il = dn.GetILGenerator();
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldc_I4_2);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stelem_Ref);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Box, typeof(int));
            il.Emit(OpCodes.Stelem_Ref);
            il.Emit(OpCodes.Call, typeof(Form1).GetMethod("call"));
            il.Emit(OpCodes.Unbox_Any, typeof(int));
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Br_S);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);
            var del = dn.CreateDelegate(typeof(Func<string, int, int>));
            del.DynamicInvoke("test",77);
Answer 1

Прямая работа с Reflection.Emit обычно используется в особых случаях, когда нужно сгенерировать сложный многострочный метод. Для однострочного метода, который только вызывает другой метод, можно использовать более простое высокоуровневое средство - деревья выражений. При их использовании не нужно задумываться об отдельных инструкциях и корректности IL. Вот пример, также для более простого случая, когда метод call - статический:

using System;
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
namespace ConsoleApplication1
{
    class Program
    {
        public static object call(object[] args)
        {
            //...
        }
        static void Main(string[] args)
        {
            ParameterExpression param1 = Expression.Parameter(typeof(string), "v1");
            ParameterExpression param2 = Expression.Parameter(typeof(int), "v2");
            NewArrayExpression expr_arr = Expression.NewArrayInit(
                typeof(object),
                Expression.Convert(param1,typeof(object)),
                Expression.Convert(param2, typeof(object))
                );
            MethodCallExpression call_expr = Expression.Call(typeof(Program).GetMethod("call"), expr_arr);
            UnaryExpression conv_expr = Expression.Convert(call_expr, typeof(int));
            var f_expr = Expression.Lambda<Func<string, int, int>>(conv_expr, param1,param2);
            Func<string, int, int> f = f_expr.Compile();
            int res = f("test", 77);
        }
    }  
}

При этом деревья выражений внутри также используют Reflection.Emit и динамические методы. Тип времени выполнения метода f.Method будет System.Reflection.Emit.DynamicMethod+RTDynamicMethod, он также будет создан в специальной изолированной системной сборке и может быть выгружен сборщиком мусора, когда он больше не нужен.

Answer 2

Ответ лежит рядом. Все комментарии выше верны, но вы создаете динамический метод DynamicMethod, который не принадлежит ни одному экземпляру класса, к которому принадлежат call() и test(). Это не плохо и не хорошо: ваш метод просто висит в воздухе. То есть ваш emit создает примерно следующее:

class Test{
    public static int test(string v1, int v2)
    { 
        return (int)call(new object[]{ v1, v2 });
    }    
}
class Call{
    public object call(object[] args)
    {
        // что-то возвращающее int
    }
}

Такое не скомпилируется

Чтобы обратиться к методу call экземпляра вашего класса необходимо либо загрузить (либо передать) в ваш метод экземпляр класса Call (в вашем случае, это, видимо, Form1), либо создать динамический метод call подобно тому, как вы создали test, либо сделать call статическим. Последний способ наиболее простой:

class Program
{
    static void Main(string[] args)
    {
        var a = new MyClass().test("d", 5);
        Console.WriteLine(a);
        Type[] arg_types = new Type[] { typeof(string), typeof(int) };
        var dn = new DynamicMethod("myMethod", typeof(int), arg_types, typeof(MyClass));
        var il = dn.GetILGenerator();                        
        il.Emit(OpCodes.Ldc_I4_2);
        il.Emit(OpCodes.Newarr, typeof(object));
        il.Emit(OpCodes.Dup);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Stelem_Ref);
        il.Emit(OpCodes.Dup);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Box, typeof(int));
        il.Emit(OpCodes.Stelem_Ref);
        il.Emit(OpCodes.Call, typeof(MyClass).GetMethod("call"));
        il.Emit(OpCodes.Unbox_Any, typeof(int));            
        il.Emit(OpCodes.Ret);
        var del = dn.CreateDelegate(typeof(Func<string, int, int>));
        var r = del.DynamicInvoke("test", 77);
        Console.WriteLine(r);

        Console.ReadKey();
    }
}
class MyClass
{
    public int test(string v1, int v2)
    {
        var a = call(new object[] { v1, v2 });
        return (int)a;
    }
    public static object call(object[] args)
    {
        // что-то возвращающее int
        return 5;
    }
}

Обратите внимание, что я использовал перегрузку DynamicMethod с указанием типа. Согласно документации - это тип, к открытым методам которого динамический метод будет иметь доступ. (скорее всего, myMethod будет динамически добавлен как статический метод этого типа. Но это не точно. Довольно любопытная штука).

READ ALSO
Добавление объектов в CollectionView внутри ObservableCollection

Добавление объектов в CollectionView внутри ObservableCollection

Не могу добавить объект в CollectionView, которая находится внутри объекта, который находится в ObservableCollectionМного прошерстил интернетов, натыкался...

132
Как использовать ключ RSA в AES шифрование?

Как использовать ключ RSA в AES шифрование?

Никак не могу сообразить как мне правильно сделать реализацию чтобы впихнуть ключ RSA 2048 в шифрование AES

89
Ошибка в запросе SQL (MySql 8.0.17) | C#

Ошибка в запросе SQL (MySql 8.0.17) | C#

Я пытаюсь сделать SELECT * FROM member WHERE id = 627528083 запрос к базе данных, но мне выдаёт ошибку синтаксиса в строке 41, почему?

79
Зачем создавать програмно, если есть дизайнер форм?

Зачем создавать програмно, если есть дизайнер форм?

Прошу внести ясностьЯ начал изучение с# с консоли что не удивительно

88