Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2215 / 889 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 14:

Генерация MSIL

Генерация MSIL с использованием Reflection.Emit

Практически для каждой сущности в MSIL, которая может быть получена с помощью механизма рефлексии, в Reflection.Emit существует специальный класс с суффиксом Builder, который может быть использован для генерации этой сущности (например, AssemblyBuilder или TypeBuilder ). Отметим, что соглашение об образовании имен не всегда строгое - например, классу FieldInfo соответствует FieldBuilder, а не FieldInfoBuilder, как можно было бы подумать.

Классы с суффиксом Builder предназначены для генерации описываемых сущностей. В каждом таком классе есть методы, позволяющие добавлять поля, методы и прочее содержание, например:

TypeBuilder tb;
MethodBuilder mb = tb.DefineMethod("Call");
ILGenerator ilg = mb.GetILGenerator();

Класс ILGenerator, использованный в приведенном примере, уже может генерировать собственно код виртуальной машины путем использования метода Emit и различных дополнительных методов ( DefineLabel, DeclareLocal, BeginScope-EndScope и т.п.).

О последовательности генерации MSIL

Структура MSIL задает определенную последовательность генерации кода, в которой код генерируется в несколько проходов:

  • вначале создается объект AssemblyBuilder, который будет использоваться для создания сборки
  • затем при помощи метода DefineDynamicModule создаются объекты типа ModuleBuilder, которые будут использоваться для порождения входящих в сборку модулей (отметим, что в случае создания модуля, не входящего в сборку, первый шаг опускается)
  • далее необходимо создать TypeBuilders для классов всех уровней (в том числе, и для вложенных классов); при этом конкретный порядок создания классов может быть весьма нетривиальным из-за возможности ссылок классов друг на друга в иерархии наследования
  • затем создаются билдеры для методов и полей классов
  • и вот только в этот радостный момент можно генерировать собственно код методов

Такая последовательность шагов при генерации является практически единственно возможной, поскольку на каждом следующем этапе нам может понадобиться сослаться на сущности, созданные на предыдущем этапе. Например, для создания поля нам нужно иметь возможность сослаться на его тип, а для генерации кода - иметь возможность сослаться на используемые поля и методы.

Генерация кода виртуальной машины

class LowLevelSample {
    public static void Run() {
	int i;
	i = 0;
	try {
	   Start:
             if (i == 10) throw new Exception();
             Console.WriteLine (i);
             i = i + 1;
             goto Start;
	}
	catch (Exception) { 
          Console.WriteLine ("Finished");
	}    
    }
}

В качестве примера генерации кода для виртуальной машины попробуем породить код на MSIL для следующей программы на C#:

class LowLevelSample {
    public static void Run() {
	int i;
	i = 0;
	try {
	   Start:
             if (i == 10) throw new Exception();
             Console.WriteLine (i);
             i = i + 1;
             goto Start;
	}
	catch (Exception) { 
          Console.WriteLine ("Finished");
	}
    }
}

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

Создание сборки и класса

Создание сборки и класса

Вначале мы создаем сборку, в которой будет находиться модуль, содержащий наш класс:

AppDomain ad = System.Threading.Thread.GetDomain();
AssemblyName an = new AssemblyName();
an.Name=System.IO.Path.GetFileNameWithoutExtension("numbers.exe");
AssemblyBuilder ab = ad.DefineDynamicAssembly
               (an,  AssemblyBuilderAccess.RunAndSave);
ModuleBuilder modb = ab.DefineDynamicModule
               ("numbers.exe", "numbers.exe");
modb.CreateGlobalFunctions();TypeBuilder classb = modb.DefineType ("Sample");
MethodBuilder mb = classb.DefineMethod 
		("Run",
		 MethodAttributes.Public|MethodAttributes.Static,
		 typeof (void), null);

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

AppDomain ad = System.Threading.Thread.GetDomain();

Далее мы создаем имя сборки:

AssemblyName an = new AssemblyName();
an.Name = System.IO.Path.GetFileNameWithoutExtension ("numbers.exe");

Теперь создаем класс, генерирующий сборку, и используем его для создания модуля, который будет содержать наш класс (отметим, что атрибут RunAndSave означает, что данная сборка может быть исполнена или сохранена на диске):

AssemblyBuilder ab = ad.DefineDynamicAssembly
             (an,  AssemblyBuilderAccess.RunAndSave);
ModuleBuilder modb = ab.DefineDynamicModule ("numbers.exe", "numbers.exe");
modb.CreateGlobalFunctions ();

Далее мы создаем билдер класса Sample и создаем в этом классе метод Run. Первый параметр DefineMethod задает имя метода, второй - атрибуты метода (в нашем случае - public и static ), затем задается тип результата метода и массив типов параметров (если параметры у метода отсутствуют, как в нашем примере, то можно передать пустой указатель).

TypeBuilder classb = modb.DefineType ("LowLevelSample");
MethodBuilder mb = classb.DefineMethod 
		("Run",
		 MethodAttributes.Public|MethodAttributes.Static,
		 typeof (void), null);