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

Генерация MSIL

Генерация кода: исключения, метки

Теперь мы можем перейти к собственно генерации кода; генерируется присваивание, начинается блок try, определяется и ставится метка

ILGenerator il = mb.GetILGenerator();
LocalBuilder var_i = il.DeclareLocal (typeof(int));
il.Emit (OpCodes.Ldc_I4_0);
il.Emit (OpCodes.Stloc, var_i);
Label EndTry = il.BeginExceptionBlock();
Label Start = il.DefineLabel();
il.MarkLabel (Start);

Теперь мы можем создать генератор кода и приступить к порождению машинного кода для метода Run. Класс ILGenerator содержит в себе методы, необходимые для порождения команд MSIL. Далее мы создаем локальную переменную, на которую можем дальше ссылаться с использованием ее билдера var_I. В случае наличия вложенных блоков их рекомендуется ограничивать вызовами BeginScope() и EndScope().

ILGenerator il = mb.GetILGenerator();
LocalBuilder var_i = il.DeclareLocal (typeof (int));

Теперь мы генерируем присваивание значения 0 в эту переменную. Метод Emit является основным методом для генерации кода MSIL и в качестве своего первого параметра принимает код команды. OpCodes содержит константы, соответствующие всем командам MSIL. Отметим, что метод Emit перегружен и может иметь различные параметры, тип которых зависит от генерируемой команды. Например, в последующем операторе мы используем параметр типа LocalBuilder для ссылки на локальную переменную:

il.Emit (OpCodes.Ldc_I4_0);
il.Emit (OpCodes.Stloc, var_i);

Для оформления try-catch блока используется метода BeginExceptionBlock , который "открывает" блок обработки исключений и создает метку, на которую впоследствии можно будет сослаться.

Label EndTry = il.BeginExceptionBlock();

Теперь мы заводим метку, которая понадобится для организации цикла. Интересно, что метод DefineLabel не генерирует никакого кода, а просто создает значение типа Label , которое в дальнейшем может быть использовано для генерации переходов. Рано или поздно метка должна быть привязана к позиции в MSIL при помощи вызова MarkLabel. Таким образом, DefineLabel соответствует объявлению идентификатора метки (в явном виде такая операция есть только в Pascal), в то время как MarkLabel соответствует определению (установке) метки.

Label Start = il.DefineLabel();
il.MarkLabel (Start);

Генерация кода: условный оператор

Далее мы генерируем условный оператор, исключение и завершаем цикл:

Label Next = il.DefineLabel();
il.Emit (OpCodes.Ldloc, var_i);
il.Emit (OpCodes.Ldc_I4, 10);
il.Emit (OpCodes.Bne_Un_S, Next);

 Type[] no_types = new Type [0];
il.Emit (OpCodes.Newobj, typeof 
(System.Exception).GetConstructor (no_types));
il.ThrowException (typeof (System.Exception));
il.MarkLabel (Next);

il.Emit (OpCodes.Ldloc, var_i);
Type [] arg = new Type [1];
arg [0] = typeof (int);
MethodInfo writeLine = typeof(System.Console).GetMethod    
      ("WriteLine", arg);
il.EmitCall (OpCodes.Call, writeLine, null);
...

Теперь нам необходимо оттранслировать условный оператор. Так как никаких if'ов в ассемблере MSIL нет (кроме условных переходов), то необходимо предварительно преобразовать этот оператор к следующей форме: if (i!=10) goto Next , где Next - это специальная метка, расположенная после ветки then условного оператора.

Label Next = il.DefineLabel();
il.Emit (OpCodes.Ldloc, var_i);
il.Emit (OpCodes.Ldc_I4, 10);
il.Emit (OpCodes.Bne_Un_S, Next);

Затем мы создаем новый экземпляр System.Exception: метод GetConstructor по массиву, содержащему типы параметров конструктора, находит в типе соответствующий конструктор. В нашем случае аргументов у конструктора нет, поэтому массив пуст. После этих действий можно спокойно поставить метку Next:

Type[] no_types = new Type [0];
il.Emit (OpCodes.Newobj, typeof (System.Exception).GetConstructor (no_types));
il.ThrowException (typeof (System.Exception));
il.MarkLabel (Next);

Далее генерируется вывод значения переменной i на консоль. Процесс мало отличается от генерации создания нового объекта, за исключением того, что мы вызываем не конструктор, а метод, и, следовательно, должны задать его имя:

il.Emit (OpCodes.Ldloc, var_i);
Type[] arg = new Type [1];
arg [0] = typeof (int);
MethodInfo writeLine = typeof (System.Console).GetMethod ("WriteLine", arg);
il.EmitCall (OpCodes.Call, writeLine, null);

il.Emit (OpCodes.Ldloc, var_i); // i = i + 1;
il.Emit (OpCodes.Ldc_I4_1);
il.Emit (OpCodes.Add);
il.Emit (OpCodes.Stloc, var_i);

Наконец, генерируется переход к следующей итерации цикла ( goto Start ):

il.Emit (OpCodes.Br, Start);