IL_0011: ret
} // end of method '<Program>$'::'<Main>$'
Роль меток в коде CIL
Вы определенно заметили, что каждая строка в коде реализации предваряется лексемой в форме
IL_X
XX: (например,
IL_0000:
,
IL_0001:
и т.д.). Такие лексемы называются
метками кода и могут именоваться в любой выбранной вами манере (при условии, что они не дублируются внутри области действия члена). При сбросе содержимого сборки в файл утилита
ildasm.exe
автоматически генерирует метки кода, которые следуют соглашению об именовании вида
IL_XXXX:
. Однако их можно заменить более описательными маркерами, например:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
Nothing_1: nop
Load_String: ldstr "Hello CIL code!"
PrintToConsole: call void [System.Console]System.Console::WriteLine(string)
Nothing_2: nop
WaitFor_KeyPress: call string [System.Console]System.Console::ReadLine()
RemoveValueFromStack: pop
Leave_Function: ret
}
В сущности, большая часть меток кода совершенно не обязательна. Единственный случай, когда метки кода по-настоящему необходимы, связан с написанием кода CIL, в котором используются разнообразные конструкции ветвления или организации циклов, т.к. с помощью меток можно указывать, куда должен быть направлен поток логики. В текущем примере все автоматически сгенерированные метки кода можно удалить безо всяких последствий:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
nop
ldstr "Hello CIL code!"
call void [System.Console]System.Console::WriteLine(string)
nop
call string [System.Console]System.Console::ReadLine()
pop
ret
}
Взаимодействие c CIL: модификация файла *.il
Теперь, когда вы имеете представление о том, из чего состоит базовый файл CIL, давайте завершим эксперимент с возвратным проектированием. Цель здесь довольно проста: изменить сообщение, которое выводится в окно консоли. Вы можете делать что-то большее, скажем, добавлять ссылки на сборки или создавать новые классы и методы, но мы ограничимся простым примером.
Чтобы внести изменение, вам понадобится модифицировать текущую реализацию операторов верхнего уровня, созданную в виде метода
<Main>$()
. Отыщите этот метод в файле
*.il
и измените сообщение на
"Hello from altered CIL code!"
.
Фактически код CIL был модифицирован для соответствия следующему определению на языке С#:
static void Main(string[] args)
{
Console.WriteLine("Hello from altered CIL code!");
Console.ReadLine();
}
Компиляция кода CIL
Предшествующие версии .NET позволяли компилировать файлы
*.il
с применением утилиты ilasm.exe. В .NET Core положение дел изменилось. Для компиляции файлов
*.il
вы должны использовать тип проекта
Microsoft.NET.Sdk.IL
. На момент написания главы он все еще не был частью стандартного комплекта SDK.
Начните с создания нового каталога на своей машине. Создайте в этом каталоге файл
global.json
, который применяется к текущему каталогу и всем его подкаталогам. Он используется для определения версии комплекта SDK, которая будет задействована при запуске команд .NET Core CLI. Модифицируйте содержимое файла, как показано ниже:
{
"msbuild-sdks": {
"Microsoft.NET.Sdk.IL": "5.0.0-preview.1.20120.5"
}
}
На следующем шаге создается файл проекта. Создайте файл по имени
RoundTrip.ilproj
и приведите его содержимое к следующему виду:
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<MicrosoftNetCoreIlasmPackageVersion>
5.0.0-preview.1.20120.5
</
MicrosoftNetCoreIlasmPackageVersion>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
</Project>
Наконец, скопируйте созданный файл
RoundTrip.il
в каталог проекта. Скомпилируйте сборку с применением .NET Core CLI:
dotnet build
Результирующие файлы будут находиться, как обычно, в подкаталоге
bin\debug\net5.0
. На этом этапе новое приложение можно запустить. Разумеется, в окне консоли отобразится обновленное сообщение. Хотя приведенный простой пример не является особенно впечатляющим, он иллюстрирует один из сценариев применения возвратного проектирования на CIL.
Директивы и атрибуты CIL
Теперь, когда вы знаете, как преобразовывать сборки .NET Core в файлы
*.il
и компилировать файлы
*.il
в сборки, можете переходить к более детальному исследованию синтаксиса и семантики языка CIL. В последующих разделах будет поэтапно рассматриваться процесс создания специального пространства имен, содержащего набор типов. Тем не менее, для простоты типы пока не будут иметь логики реализации своих членов. Разобравшись с созданием простых типов, можете переключить внимание на процесс определения "реальных" членов с использованием кодов операций CIL.