Дата публикации статьи: 29.07.2006 13:46

Разъяснение промежуточного языка 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.