Чтение онлайн

ЖАНРЫ

ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Троелсен Эндрю

Шрифт:

 // в локальной переменной [2].

 stloc.2

 ret

}

Как видите, в CIL при размещении локальных переменных сначала используется директива .locals с атрибутом init. При этом в скобках каждая переменная связывается со своим числовым индексом (здесь это [0], [1] и [2]). Каждый индекс идентифицируется типом данных и (необязательно) именем переменной. После определения локальных переменных соответствующее значение загружается в стек (с помощью подходящих кодов операций, связанных с загрузкой) и запоминается в локальной переменной (с помощью подходящих

кодов операций для сохранения значений).

Связывание параметров с локальными переменными

Вы только что видели, как в CIL с помощью .local init объявляются локальные переменные, однако нужно еще выяснить, как передать отступающие параметры локальным методом. Рассмотрим следующий статический метод C#.

public static int Add(int a, int b) {

 return a + b;

}

Этот внешне "невинный" метод в терминах CIL существенно более "многословен". Во-первых, поступающие аргументы (а и b) следует поместить в виртуальный стек выполнение с помощью кода операций ldarg (загрузка аргумента). Затем используется код операции add, чтобы извлечь два значения из стека, найти сумму и снова сохранить значение в стеке. Наконец, эта сумма извлекается из стена и возвращается вызывающей стороне с помощью кода операции ret. Если дизассемблировать указанный метод C# с помощью ildasm.exe, вы обнаружите, что компилятор csc.exe добавляет множество дополнительных лексем, хотя сущность CIL-кода оказывается исключительно простой.

.method public hidebysig static int32 Add(int32 a, int32 b) cil managed {

 .maxstack 2

 ldarg.0 // Загрузка 'a' в стек,

 ldarg.1 // Загрузка 'b' стек,

 add // Сложение этих значений.

 ret

}

Скрытая ссылка this

Обратите внимание на то, что в рамках программного кода CIL для ссылок на два входных аргумента (а и b) используются их индексы позиции (индекс 0 и индекс 1, поскольку индексация в виртуальном стеке выполнения начинается с нуля).

При анализе программного кода и его создании непосредственно в CIL следует быть очень внимательным, поскольку каждый (нестатический) метод, имеющий входные аргументы, автоматически получает неявный дополнительный параметр, который является ссылкой на текущий объект (это должно вызвать аналогию с ключевым словом C# this). Поэтому, если определить метод Add, как нестатический

// Уже не является статическим!

public int Add(int a, int b) {

 return a + b;

}

то входные аргументы а и b будут загружаться с помощью ldarg.1 и ldarg.2 (а не с помощью ожидаемых ldarg.0 и ldarg.1). Причина как раз в том, что ячейка 0 будет содержать неявную ссылку this. Рассмотрите следующий псевдокод.

// Это только псевдокод!

.method public hidebysig static int32 AddTwoIntParams(MyClass_HiddenThisPointer this, int32 a, int32 b) cil managed {

 ldarg.0 // Загрузка MyClass_HiddenThisPointer в стек,

 ldarg.1 // Загрузка 'а' в стек.

 ldarg.2 //
Загрузка 'b' в стек.

 …

}

Представление итерационных конструкций

Итерационные конструкции в языке программирования C# представляются с помощью ключевых слов for, foreach, while и do, каждое из которых имеет свое специальное представление в CIL. Рассмотрим классический цикл for.

public static void CountToTen {

 for (int i = 0; i ‹ 10; i++);

}

Вы можете помнить о том, что коды операций br (br, blt и т.д.) используются для управления потоком программы в зависимости от выполнения некоторого условия. В нашем примере мы задали условие, по которому должен произойти выход из цикла, когда значение локальной переменной i станет равным 10. С каждым проходом к значению i добавляется 1, после чего сразу же выполняется тестовое сравнение.

Также напомним, что при использовании любых кодов операций CIL, связанных с ветвлением, нужно определить метку для обозначения в программном коде места, куда следует перейти в случае выполнения условия. С учетом этого рассмотрите следующий (расширенный) программный код CIL, сгенерированный с помощью ildasm.exe (включая и метки программного кода).

.method public hidebysig static void CountToTen cil managed {

 .maxstack 2

 .locals init ([0] int32 i) // Инициализация локальной целой 'i'.

 IL_0000: ldc.i4.0 // Загрузка этого значения в стек.

 IL_0001: stloc.0 // Сохранение значения под индексом '0'.

 IL_0002: br.s IL_0008 // Переход к IL_0008.

 IL_0004: ldloc.0 // Загрузка значения с индексом 0.

 IL_0005: ldc.i4.1 // Загрузка значения '1' в стек.

 IL_0006: add // Добавление в стек под индексом 0.

 IL_0007: stloc.0

 IL_0008: ldloc.0 // Загрузка значения с индексом '0'.

 IL_0009: ldc.i4.s 10 // Загрузка значения '10' в стек.

 IL_000b: blt.s IL_0004 // Меньше? Если да, то к 1L_0004.

 IL_000d: ret

}

В сущности, этот программный код CIL начинается с определения локальной переменной int32 и загрузки ее в стек. Затем осуществляются переходы между командами с метками IL_0008 и IL_0004, причем каждый раз значение i увеличивается на 1 и проверяется, осталось ли это значение меньше 10. Если нет, то происходит выход из метода.

Создание компоновочного блока .NET в CIL

Теперь, освоив синтаксис и семантику CIL, вы можете закрепить свои знания на практике, построив приложение .NET с использованием только CIL и текстового редактора. Ваше приложение будет состоять из приватного одномодульного *.dll, содержащего два определения типов класса, и консольного *.exe, взаимодействующего с этими типами.

Создание CILCars.dll

Первым делом следует построить файл *.dll для использования клиентом. Откройте любой текстовый редактор и создайте новый файл *.il с именем CILCars.il. Этот одномодульный компоновочный блок будет использовать два внешних двоичных файла .NET, поэтому вы можете начать свой файл программного кода CIL так.

Поделиться с друзьями: