Создание сборки .NET на CIL
72C# --- Сборки .NET --- Создание сборки .NET на CIL
Ознакомившись с синтаксисом и семантикой языка CIL, пришла пора закрепить изученный материал, создав .NET-приложение с использованием одной только утилиты ilasm.ехе и предпочитаемого текстового редактора. Это приложение будет состоять из приватно развертываемой однофайловой сборки *.dll, содержащей определения двух типов классов, и консольной сборки *.ехе, взаимодействующей с этими типами.
В первую очередь необходимо создать сборку *.dll, которую будет использовать клиент. Для этого откройте текстовый редактор и создайте новый файл *.il. В этой однофайловой сборке будут использоваться две внешних сборки .NET.
В этой сборке будет содержаться два типа класса. Первый называется UserInfo и имеет два поля данных и специальный конструктор, а второй — WriteInfoUser и имеет единственный статический метод по имени DisplayUserInfo(), принимающий UserInfo в качестве параметра и возвращающий void. Оба они должны размещаться в пространстве имен UserInfo. CIL-код данной программы представлен ниже:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 4:0:0:0
}
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 4:0:0:0
}
// Определяем сборку
.assembly UserInfo
{
.hash algorithm 0x00008004
.ver 1:0:0:1
}
.module userinfo.dll
// Реализуем тип UserInfo
.namespace UserInfo
{
.class public auto ansi beforefieldinit UserInfo
extends [mscorlib]System.Object
{
// Определяем два поля
.field public string Name
.field public int32 Age
// Конструктор
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 i, string s) cil managed
{
.maxstack 8
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ldarg.0
ldarg.1
stfld int32 UserInfo.UserInfo::Age
ldarg.0
ldarg.2
stfld string UserInfo.UserInfo::Name
ret
}
}
.class public auto ansi beforefieldinit WriteUserInfo
extends [mscorlib]System.Object
{
.method public hidebysig static void
InDisplay(class UserInfo.UserInfo ui) cil managed
{
.maxstack 8
// Используем локальную переменную
.locals init ([0] string caption)
ldstr "Имя: {0}\n"
ldstr "Возраст: {1}"
ldarg.0
// Помещаем в стек значение передаваемого аргумента
ldfld string UserInfo.UserInfo::Name
call string [mscorlib]System.String::Format(string, object)
stloc.0
ldarg.0
ldflda int32 UserInfo.UserInfo::Age
call instance string [mscorlib]System.Int32::ToString()
ldloc.0
// Вызов метода MessageBox.Shadow()
call valuetype [System.Windows.Forms]
System.Windows.Forms.DialogResult
[System.Windows.Forms]
System.Windows.Forms.MessageBox::Show(string, string)
pop
ret
}
}
}
Теперь можно скомпилировать новую сборку *.dll с помощью ilasm.exe и проверить содержащийся внутри нее CIL-код на предмет правильности с семантической точки зрения с помощью утилиты peverify.exe.
Далее можно создать простую сборку *.ехе с методом Main() внутри, который будет создавать объект UserInfo и передавать его статическому методу WriteUserInfo.IsDisplay(). Для этого создадим новый файл *.il, добавим в него ссылки на внешние сборки mscorlib.dll и UserInfo.dll (не забыв поместить копию последней в каталог клиентского приложения) и определим в нем единственный тип (Program), в котором будут производиться все необходимые манипуляции над сборкой UserInfo.dll. Ниже показан полный код:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 4:0:0:0
}
.assembly extern UserInfo
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 4:0:0:0
}
// Определение сборки
.assembly UserInfo
{
.ver 1:0:0:1
}
.module UserInfo.exe
.namespace Program
{
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main() cil managed
{
// Точка входа в *.exe
.entrypoint
.maxstack 8
.locals init ([0] class [UserInfo]UserInfo.UserInfo myUser)
ldc.i4 26
ldstr "Alex"
newobj instance void [UserInfo]UserInfo.UserInfo::.ctor(int32, string)
stloc.0
ldloc.0
// Вызов метода InDisplay()
call void [UserInfo]
UserInfo.WriteUserInfo::InDisplay(
class [UserInfo]UserInfo.UserInfo)
ret
}
}
}
Единственным кодом операции, на который здесь важно обратить внимание, является .entrypoint. Этот код применяется для обозначения того, какой из методов должен быть входной точкой в модуле *.ехе. В действительности, поскольку по .entrypoint CLR-среда определяет начальный метод для выполнения, этот метод может иметь какое угодно имя, хотя для него было использовано стандартное имя Main(). В остальной части CIL-кода этого метода Main() производятся типичные операции по помещению и извлечению значений из стека.