Выполнить код .NET IL в С#

Есть ли способ выполнить массив IL-кодов в С#, таких как коды оболочки в C/С++?

Я хочу создать метод, преобразовать его в IL-код, обфускать его и сохранить в массиве байтов и, наконец, захочет выполнить его расшифровку содержимого массива и выполнить IL-код.

Например, это мой код С#:

 static int MyMethod(string A, int B)
 {
    B += 10;
    if (A.Equals("A"))
        B = 0;
    return B;
 }

Теперь я конвертирую его в IL-код:

 private static int MyMethod(string A, int B)
  {
    locals: int local_0,
            bool local_1

    /* 0000025C 00             */ nop
    /* 0000025D 03             */ ldarg_1 // int B
    /* 0000025E 1F 0A          */ ldc_i4_s 10
    /* 00000260 58             */ add
    /* 00000261 10 01          */ starg_s arg_1 // int B
    /* 00000263 02             */ ldarg_0 // string A
    /* 00000264 72 01 00 00 70 */ ldstr "A"
    /* 00000269 6F 11 00 00 0A */ callvirt System.String::Equals(string) // returns bool
    /* 0000026E 16             */ ldc_i4_0
    /* 0000026F FE 01          */ ceq
    /* 00000271 0B             */ stloc_1 // bool local_1
    /* 00000272 07             */ ldloc_1 // bool local_1
    /* 00000273 2D 03          */ brtrue_s loc_28
    /* 00000275 16             */ ldc_i4_0
    /* 00000276 10 01          */ starg_s arg_1 // int B
loc_28:
    /* 00000278 03             */ ldarg_1 // int B
    /* 00000279 0A             */ stloc_0 // int local_0
    /* 0000027A 2B 00          */ br_s loc_32
loc_32:
    /* 0000027C 06             */ ldloc_0 // int local_0
    /* 0000027D 2A             */ ret
  }

И, наконец, это массив байтов:

private byte[] ilcode = 
{
   0x00, 0x03, 0x1F, 0x0A, 0x58, 0x10, 0x01, 0x02, 0x72, 0x01, 0x00, 0x00, 0x70, 0x6F, 0x11, 0x00, 0x0, 0x0A, 0x16,
   0xFE, 0x01, 0x0B, 0x07, 0x2D, 0x03, 0x16, 0x10, 0x01, 0x03, 0x0A, 0x2B, 0x00, 0x06, 0x2A
};

Ответы

Ответ 1

Вам нужно будет использовать инфраструктуру в пространстве имен System.Reflection.Emit. В частности, вы должны посмотреть docs для MethodBuilder.CreateMethodBody, который принимает массив байтов, представляющий инструкции MSIL. Здесь есть полный пример, но ниже приведен короткий фрагмент его использования. Здесь я также создам делегат для динамического метода.

Отмечу, что это поддерживается только очень ограниченным образом, который вызывается в документах:

В настоящее время это не полностью поддерживается. Пользователь не может предоставить местоположение исправлений фишек и обработчиков исключений.

Проблема заключается в том, что токены метаданных, используемые в IL для ссылочных типов, методов, строковых литералов и т.д., разрешаются на уровне модуля. Таким образом, IL не полностью переносится в том смысле, что вы не можете взять произвольный, необработанный IL из метода в одном модуле и просто отбросить его в другой метод в другом модуле. В новом модуле нужны жетоны метаданных. Однако, если вы знаете, что ваш IL не содержит токенов метаданных, вы можете это сделать, но это сильно ограничивает то, что вы можете сделать с этим. (HT: svick, Саймон Свенссон)

class Program
{
    static void Main(string[] args)
    {
        // opcodes for pushing two arguments to the stack, adding, and returning the result.
        byte[] ilcodes = { 0x02, 0x03, 0x58, 0x2A };
        var method = CreateFromILBytes(ilcodes);
        Console.WriteLine(method(2, 3));
    }

    private static Func<int, int, int> CreateFromILBytes(byte[] bytes)
    {
        var asmName = new AssemblyName("DynamicAssembly");
        var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);
        var module = asmBuilder.DefineDynamicModule("DynamicModule");
        var typeBuilder = module.DefineType("DynamicType");
        var method = typeBuilder.DefineMethod("DynamicMethod", 
            MethodAttributes.Public | MethodAttributes.Static, 
            typeof(int), 
            new[] { typeof(int), typeof(int) });
        method.CreateMethodBody(bytes, bytes.Length);
        var type = typeBuilder.CreateType();
        return (Func<int, int, int>)type.GetMethod("DynamicMethod").CreateDelegate(typeof(Func<int, int, int>));
    }
}

Обратите внимание на использование опции RunAndSave. Это позволит сохранить динамическую сборку на диск во временном месте. Может быть более желательно использовать RunAndCollect, который генерирует сборку только в памяти и позволяет собирать ее позже, когда все ссылки на нее мертвы. Однако есть некоторые предостережения для так называемых коллекционных сборок, подробные здесь.