Челночная технология разработки

Челночная технология разработки

Вы уже знаете, как использовать ildasm.exe для просмотра программного кода CIL, генерируемого компилятором C#. Однако вы можете не знать о том, что ildasm.exe позволяет записать CIL-код, содержащийся в загруженном компоновочном блоке, во внешний файл. Имея программный код CIL в своем распоряжении, вы можете отредактировать и с помощью ildasm.exe – компилятора CIL – скомпилировать базовый код вновь.

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

• Перед вами стоит задача изменить компоновочный блок, для которого нет исходного кода.

• Ввиду несовершенства компилятора языка .NET, сгенерировавшего неэффективный программный код CIL, вы хотите изменить этот код.

• Вы создаете компоновочные блоки, взаимодействующие в рамках COM, и вам приходится принимать во внимание то, что некоторые атрибуты IDL (Interface Definition Language – язык описания интерфейса) в процессе преобразования могут теряться (например, COM-атрибут [helpstring]).

Для примера использования челночной технологии разработки создайте новый файл (HelloProgram.cs) исходного кода C# с помощью обычного текстового редактора и определите в этом файле следующий тип класса (можете, конечно, использовать и Visual Studio 2005, но тогда не забудьте удалить файл AssemblyInfo.cs, чтобы уменьшить объем генерируемого CIL-кода).

// Простое консольное приложение на языке C#.

using System;

class Program {

 static void Main(string[] args) {

  Console.WriteLine("Hello CIL code!");

  Console.ReadLine();

 }

}

Сохраните этот файл в подходящем месте на своем диске и скомпилируйте его с помощью программы csc.exe.

csc HelloProgram.cs

Теперь откройте полученный файл HelloProgram.exe с помощью ildasm.exe и, используя опцию меню File?Dump, сохраните "сырой" программный код CIL в новом файле *.il (HelloProgram.il) На вашем жестком диске (значения, предлагаемые в появляющемся диалоговом окне, вполне подойдут для наших целей). Теперь вы можете рассмотреть этот файл, используя любой текстовый редактор. Вот слегка откорректированный в снабженный некоторыми комментариями результат.

// Компоновочные блоки, на которые мы ссылаемся.

.assembly extern mscorlib {

 .publickeytoken = (В7 7A 5С 56 19 34 Е0 89)

 .ver 2:0:0:0

}

// Ваш компоновочный блок.

.assembly HelloProgram {

 .hash algorithm 0х00008004

.ver 0:0:0:0

}

.module HelloProgram.exe

.imagebase 0x00400000

.file alignment 0x00000200

.stackreserve 0x00100000

.subsystem 0x0003

.corflags 0x00000001

// Определение класса Program.

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object {

 .method private hidebysig static void Main(string[] args) cil managed {

  // Обозначение этого метода, как точки входа

  // выполняемого файла.

  .entrypoint

  .maxstack.8

  IL_0000: nop

  IL_0001: ldstr "Hello CIL code!"

  IL_0006: call void [mscorlib]System.Console::WriteLine(string)

  IL_000b: nop

  IL_000c: call string [mscorlib]System.Console::ReadLine()

  IL_0011: pop

  IL_0012: ret

 }

 // Конструктор, заданный по умолчанию.

 .method public hidebysig specialname rtspecialname instance void .ctor() cil managed {

  .maxstack 8

  IL_0000: ldarg.0

  IL_0001: call instance void [mscorlib]System.Object::.ctor()

  IL_0006: ret

 }

}

Во-первых, обратите внимание на то, что файл *.il начинается с объявления всех внешних компоновочных блоков, на которые ссылается данный компоновочный блок. Здесь вы видите только одну директиву .assembly extern для одного обязательно присутствующего mscorlib.dll. Если бы ваша библиотека классов использовала типы из других внешних компоновочных блоков, вы бы обнаружили дополнительные директивы .assembly extern.

Далее следует формальное определение вашего компоновочного блока HelloProgram.exe, для которого указана версия 0.0.0.0, назначаемая по умолчанию (если вы не укажете иное значение с помощью атрибута [AssemblyVersion]). После этого приводятся другие описания компоновочного блока, для которых используются другие директивы CIL (такие, как .module, .imagebase и т.д.).

После указания ссылок на внешние компоновочные блоки и определения текущего компоновочного блока идет определение типа Program. Обратите внимание на то, что директива.class имеет несколько атрибутов (многие из которых необязательны), – например, атрибут extends, задающий базовый класс типа.

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object {…}

Большой кусок программного кода CIL соответствует конструктору класса, заданному по умолчанию, и методу Main(). Оба они определены (в частности) с помощью директивы .method. После определения этих членов с помощью подходящих директив и атрибутов они реализуются с помощью различных кодов операций.

Важно понять, что в CIL при взаимодействии с типами .NET (например, с System.Console) всегда необходимо использовать абсолютные имена типов. Более того, к абсолютному имени типа всегда должен добавляться (в квадратных скобках) префикс с понятным именем компоновочного блока, определяющего этот тип. Взгляните на CIL-реализацию Main().

.method private hidebysig static void Main(string[] args) cil managed {

 .entrypoint

 .maxstack 8

 IL_0000: nop

 IL_0001: ldstr "Hello CIL code!"

 IL_0006: call void [mscorlib]System.Console::WriteLine(string)

 IL_000b: nop

 IL_000c: call string [mscorlib]System.Console::ReadLine()

 IL_0011: pop

 IL_0012: ret

}

Реализация конструктора, заданного по умолчанию, в терминах программного кода CIL включает еще одну относящуюся к загрузке инструкцию (ldarg.0). В данном случае значение загружается в стек не как пользовательская переменная, указанная нами, а как текущая объектная ссылка (подробности этого процесса будут описаны позже). Также обратите внимание на то, что конструктор, заданный по умолчанию, явно вызывает конструктор базового класса.

.method public hidebysig specialname rtspecialname instance void .ctor cil managed {

 .maxstack 8

 IL_0000: ldarg.0

 IL_0001: call instance void [mscorlib]System.Object::.ctor()

 IL_0006: ret

}