Ознакомившись с синтаксисом и семантикой языка 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() производятся типичные операции по помещению и извлечению значений из стека.