Двунаправленное проектирование
69C# --- Сборки .NET --- Двунаправленное проектирование
Утилита ildasm.exe предназначена для просмотра генерируемого компилятором C# кода CIL. Эта утилита также позволяет сбрасывать CIL-код, содержащийся внутри загруженной сборки, во внешний файл. Полученный подобным образом CIL-код можно легко редактировать и затем компилировать с помощью компилятора CIL (ilasm.exe).
Вспомните, что для просмотра CIL-кода любой отдельно взятой сборки, а также его преобразования в примерную кодовую базу на C# также можно применять утилиту reflector.exe. Формально такой подход называется двунаправленным проектированием (roundtrip engineering) и может оказываться полезным в перечисленных ниже ситуациях:
Необходимость изменить сборку, исходный код которой больше не доступен.
Необходимость изменить неэффективный (или предельно некорректный) CIL-код, полученный в результате использования далекого от идеала компилятора языка .NET.
Необходимость при создании сборок, взаимодействующих с СОМ, вернуть некоторые из СОМ-атрибутов на языке IDL (Interface Definition Language — язык описания интерфейсов), которые были утрачены в процессе преобразования (вроде СОМ-атрибута [helpstring]).
Чтобы попробовать процесс двунаправленного проектирования на практике, создадим в простом текстовом редакторе новый файл кода C# и определим в нем показанный ниже класс (при желании можно создать проект Console Application в Visual Studio 2010 и удалить из него файл AssemblyInfo.cs, уменьшив объем генерируемого CIL-кода):
using System;
class Program
{
static void Main()
{
Console.WriteLine("My CIL-code");
Console.ReadLine();
}
}
Сохраним этот файл в удобном месте и скомпилируем программу с помощью csc.ехе:
csс Program.cs
Откроем полученный в результате файл Program.exe в утилите ildasm.exe и выберем в меню File (Файл) пункт Dump (Дамп), чтобы сохранить CIL-код в новом файле с расширением *.il (Program.il) в той же папке, где находится скомпилированная сборка (не изменяя значений, предлагаемых по умолчанию в диалоговом окне сохранения).
При сбросе содержимого сборки в файл утилита ildasm.exe генерирует и файл *.res. Файлы подобного рода можно спокойно игнорировать (и удалять), поскольку они использоваться не будут.
Теперь можно просмотреть этот файл в любом текстовом редакторе. Ниже показано его содержимое:
// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.1
// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly program
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module program.exe
// MVID: {304BE092-B12B-479A-A5B9-68902DA41433}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x04B30000
// =============== CLASS MEMBERS DECLARATION ===================
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Размер кода: 19 (0x13)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "My 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
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Размер кода: 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
} // end of class Program
// =============================================================
// *********** ДИЗАССЕМБЛИРОВАНИЕ ЗАВЕРШЕНО ***********************
// ВНИМАНИЕ: создан файл ресурсов Win32 C:\program.res
Прежде всего, обратите внимание, что файл *.il начинается с объявления всех внешних сборок, на которые ссылается текущая скомпилированная сборка. В данном случае присутствует только одна лексема .assembly extern, которая указывает на всегда добавляемую внешнюю сборку mscorlib.dll. Разумеется, если бы в библиотеке классов использовались типы из каких-то других сборок, здесь бы присутствовали и другие директивы .assembly extern.
Далее идет формальное определение самой сборки Program.exe, с назначенным по умолчанию номером версии 0.0.0.0 (поскольку никакой номер версии с помощью атрибута [AssemblyVersion] не был указан). Это определение сопровождается несколькими директивами CIL (.module, .imagebase и т.д.).
После списка внешних сборок и определения текущей сборки идет определение типа Program. Обратите внимание на наличие внутри директивы .class различных атрибутов (многие из которых необязательны), таких как extends, который указывает базовый класс для типа:
.class private auto ansi beforefleldinit Program extends [mscorlib]System.Ob]ect
(...)
Большую часть остального CIL-кода занимает описание реализации конструктора, который должен применяться для данного класса по умолчанию, и метода Main(); оба они определяются (частично) с помощью директивы .method. После определения этих членов с помощью соответствующих директив и атрибутов, они реализуются с помощью различных кодов операций.
Очень важно запомнить, что при взаимодействии с типами .NET (такими как System.Console) в CIL должно всегда использоваться полностью квалифицированное имя типа. Более того, к этому полностью квалифицированному имени необходимо всегда в качестве префикса присоединять дружественное имя сборки, в которой находится его определение (в квадратных скобках).
Метки CIL
Наверняка вам бросилось в глаза, что каждая строка в коде реализации сопровождается префиксом вида IL_XXX: (IL_ОООО:, IL_0001: и т.д.). Префиксы такого вида называются метками кода и могут иметь произвольный формат (главное, чтобы они не дублировались в рамках одного и того же члена). При сбрасывании содержимого сборки в файл утилита ildasm.ехе автоматически генерирует метки кода в формате IL_XXX: в соответствии с принятым для них соглашением об именовании.
В действительности метки кода по большей части совершенно не обязательны. Единственный случай, когда их действительно требуется применять — при создании CIL-кода с различными конструкциями ветвления или циклов, поскольку они позволяют указать, куда должен быть направлен поток логики. В текущем примере можно вообще удалить все эти автоматически сгенерированные метки безо всяких последствий.