Разъяснение промежуточного языка Microsoft (Microsoft Intermediate Language). Часть 2 – Краткое описание .NET приложения
Автор: Kamran Qamar
[Оригинал статьи] [Обсудить в форуме]
Перевод с английского: Виталий Готовцов
WWW: http://www.vitgot.narod.ru
Приложение .NET состоит из одной или более исполняемых частей, каждая из которых несет метаданные и дополнительный управляемый код. Управляемые .NET приложения называются сборками. Сборка – это набор, состоящий из одного или более файлов, запускаемых, как единое целое. Сборка всегда содержит манифест, который определяет:
-
версию, название, культуру и требования безопасности сборки
-
какие еще файлы, если они вообще существуют, принадлежат сборке наряду с шифрованным хэшем каждого файла. Сам манифест скрывает часть файла в метаданных и этот файл всегда является частью сборки
-
какие типы, определенные в других файлах сборки, должны быть экспортированы из сборки. Типы, определенные в том же файле, что и манифест, экспортируются, основываясь на атрибутах самого типа
-
опционально, цифровая подпись самого манифеста и открытый ключ, используемый для ее вычисления.
На исполняемый контент можно ссылаться, как на модули. У нас может быть одномодульная сборка или многомодульная сборка. Сборка содержит только один манифест среди всех составляющих ее файлов. Для сборки, которая должна быть исполнена (а не динамически загружена) манифест скрывается в модуле, который содержит точку входа. Следующий рисунок показывает структуру односборочного .NET-приложения:
В следующей статье мы еще обсудим формат приложения .NET и увидим, как создаются многомодульные сборки. Для текущего обсуждения достаточно знать, что одно односборочное приложение состоит из секции идентификации сборки, известной как манифест, секции метаданных и секции IL-кода.
Держа этот короткий рассказ в перспективе к нашему, написанному ранее, примеру, мы понимаем, что наше приложение содержит секцию сборки, определенную с помощью директивы assembly. Однако она не полна, так как не содержит информации о версии, названии, культуре, безопасности и информации модуля. В следующих нескольких строках я усовершенствую код нашего примера, чтобы дополнить эту информацию.
.assembly DemystifyingILChapter1
{
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{
.method static void HelloWorld()
{
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
}
В этом коде я расширил директиву assembly, которая обозначает начало манифеста. Директива assembly может содержать множество других директив. Я использовал две, вот они:
-
hash: Эта директива говорит VSE, какой алгоритм хэширования мы использовали для обеспечения безопасности. Число 0х00008004 означает, что хэширование выполнено с помощью алгоритма SHA1, который используется по умолчанию. Все сборки хэшируются с помощью этого алгоритма
-
ver: Номер версии сборки, устанавливается, как четыре 32-битовых числа, разделяемых двоеточием «:» (00:00:00:00). Эти четыре числа представляют основной номер версии, дополнительный номер версии, номер редакции и номер ревизии
Во время обсуждения формата .NET-приложения мы так же упоминали, что выполняемое приложение содержит ссылку на module. До сих пор мы не использовали никаких директив, чтобы сказать ассемблеру, какой модуль создавать. В нашей программе мы ссылаемся на внешнюю программу (mscorlib). Правильно ли компилируется наша сборка?
Итак, утилита ILAsm.exe достаточно умна, чтобы автоматически определить наш код, как первичный, и связать его со сборкой mscorlib. Мы облегчим работу ассемблера и сами создадим для этих двух директиву. Чтобы сослаться на внешнюю сборку, мы снова будем использовать директиву assembly с атрибутом extern. Чтобы правильно ссылаться на сборку .NET framework минимальным требованием является исходный открытый ключ или токен открытого ключа и версия этой сборки. Токен открытого ключа – это младшие 8 бит SHA1-хэша кода, которые однозначно идентифицируют сборку. Мы можем найти эту информацию о нашей сборке, указав в окне проводника папку C:\WinNT\assembly, как показано на рисунке внизу:
На рисунке мы можем найти нашу сборку mscorlib, у меня установлена версия 1.0.3300.0 с токеном открытого ключа В77А5С561934Е089. На вашем компьютере может быть установлена другая версия mscorlib. Пожалуйста, проверьте и установите программу такой же версии.
Наш обновленный код с этими двумя добавленными директивами выглядит так:
.module Hello.exe
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 1:0:3300:0
}
.assembly DemystifyingILChapter1
{
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{
.method static void HelloWorld()
{
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
}
Теперь у нас есть правильное готовое .NET приложение, со всей необходимой для работы.NET framework информацией внутри. Затем мы напишем то же приложение HelloWorld на C# и VB.NET и сравним IL-код, сгенерированный соответствующими компиляторами.
Программа Hello World на языках высокого уровня
Мы создадим простое приложение HelloWorld на C# и VB. Мы скомпилируем каждое в исполняемые файлы. После того, как исполняемые файлы будут созданы, мы дизассемблируем их с помощью утилиты ildasm.exe. Мы сравним полученные результаты, один с программы на C#, а другой - с программы на VB.NET и увидим, можем ли мы найти связь между ними и IL-программой, которую мы создали раньше.
C#
Давайте напишем ту же программу на C#. Листинг кода дается ниже:
public class HelloWorld
{
public static void Main()
{
System.Console.WriteLine("Hello World.");
}
}
Мы можем компилировать эту программу с помощью csc.exe (компилятор C-Sharp). Теперь мы запустим программу ildasm.exe на вновь созданный файл HelloWorld.exe с помощью следующей командной строки:
ildasm /out = Helloworld.txt HelloWorld.exe
Это создаст текстовый файл HelloWorld.txt со следующим содержанием:
// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.3705.0
// Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:3300:0
}
.assembly HelloWorld
{
// --- The following custom attribute is added automatically, ---
// --- do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::
// .ctor(bool, bool) = ( 01 00 00 01 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module HelloWorld.exe
// MVID: {E63F9CA9-D4C4-4826-9BE1-2B0EE3694289}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03000000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
} // end of class HelloWorld
// =============== CLASS MEMBERS DECLARATION ===================
// note that class flags, 'extends' and 'implements' clauses
// are provided here for information only
.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 1
IL_0000: ldstr "Hello World."
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method HelloWorld::.ctor
} // end of class HelloWorld
// =============================================================
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file HelloWorld.res
Visual Basic .NET
Давайте напишем ту же программу на VB.NET, листинг приведен ниже:
Public Class HelloWorld
Public Shared Sub Main
System.Console.WriteLine ("Hello World From VB.NET")
End Sub
End Class
Скомпилируем этот код с помощью vbc.exe (VB компилятор) и дизассемблируем его, как показано ниже. Файл в результате будет содержать следующее:
// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.3705.0
// Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:3300:0
}
.assembly extern Microsoft.VisualBasic
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 7:0:3300:0
}
.assembly HelloWorld
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module HelloWorld.exe
// MVID: {BAFC36F6-4629-4BBB-88B9-B76B8D39C758}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03000000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.class public auto ansi HelloWorld
extends [mscorlib]System.Object
{
} // end of class HelloWorld
// =============================================================
// =============== CLASS MEMBERS DECLARATION ===================
// note that class flags, 'extends' and 'implements' clauses
// are provided here for information only
.class public auto ansi HelloWorld
extends [mscorlib]System.Object
{
.method public specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method HelloWorld::.ctor
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = (01 00 00 00)
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "Hello World From VB.NET"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
} // end of class HelloWorld
// =============================================================
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file HelloWorld.res
Когда вы прочитаете указанные выше файлы, вы узнаете, что все это уже было объяснено раньше. И результаты компиляторов VB.NET и C# почти идентичны. Я использовал этот пример, чтобы показать, что не зависимо от языка, который вы используете, в конечно итоге исходный код будет конвертирован в IL-код. Таким образом, различие между языками программирования стало теперь поверхностной проблемой.
В следующих статьях, я представлю вам набор IL-инструкций, расскажу, как IL используется для выполнения базовых операций, таких как Выбор, Итерация, перегрузка и т.д. Мы также увидим, как создавать ссылки и типы значений. Определим методы, свойства и индексаторы. Я покажу вам основы обработки исключений и создание специальных классов, таких как делегаты, и определять пользовательские события. Я завершу этот цикл полнофункциональным GUI-приложением, написанным на IL.