Роль CIL
Теперь, когда вы имеете начальное представление о компоновочных блоках .NET, давайте немного подробнее обсудим роль общего промежуточного языка (CIL). CIL- это язык, находящийся выше любого набора инструкций, специфического для конкретной платформы. Независимо от того, какой язык .NET вы выберете для использования, соответствующий компилятор сгенерирует инструкции CIL. Например, следующий программный код C# моделирует тривиальный калькулятор. Не пытаясь пока что полностью понять синтаксис этого примера, обратите внимание на формат метода Add() в классе Calc.
// Calc.cs
using System;
namespace CalculatorExample {
// Этот класс содержит точку входа приложения.
public class CalcApp {
static void Main() {
Calc с = new Calc();
int ans = c.Add(10, 84);
Console.WriteLine("10 + 84 is {0}.", ans);
// Ждать, пока пользователь не нажмет клавишу ввода.
Console.ReadLine();
}
}
// C#-калькулятор.
public class Calc {
public int Add(int x, int y) {return x + y;}
}
}
После того как компилятор C# (csc.exe) скомпилирует этот файл исходного кода, вы получите состоящий из одного файла компоновочный блок *.exe, который содержит манифест, CIL-инструкции и метаданные, описывающие каждый аспект классов Calc и CalcApp. Например, если вы откроете этот компоновочный блок с помощью ildasm.exe (мы рассмотрим ildasm.exe немного позже в этой же главе), вы увидите, что метод Add () в терминах CIL представляется так.
.method public hidebysig instance int32 Add(int32 x, int32 y) cil managed
{
// Code size 8 (0x8)
.maxstack 2
.locals init ([0] int32 CS$l$0000)
IL_0000: ldarg.1
IL_0001: ldarg.2
IL_0002: add
IL_0003: stloc.0
IL_0004: br.s IL_0006
IL_0006: ldloc.0
IL_0007: ret
} // end of method Calc::Add
Не беспокойтесь, если вы пока не в состоянии понять CIL-код для этого метода – в главе 15 будут описаны основы языка программирования CIL. Здесь следует сконцентрироваться на том, что компилятор C# генерирует CIL-код, а не специфические для платформы инструкции.
Напомним теперь, что это верно для всех .NET-компиляторов. Для иллюстрации предположим, что вы создали аналогичное приложение с помощью Visual Basic .NET (VB .NET), а не с помощью C#.
' Calc.vb
Imports System
Namespace CalculatorExample
' VB .NET 'Модуль' – это класс, содержащий только ' статические члены.
Module CalcApp
Sub Main()
Dim ans As Integer
Dim с As New Calc
ans = c.Add(10, 84)
Console.WriteLine("10 + 84 is {0}.", ans)
Console.ReadLine()
End Sub
End Module
Class Calc
Public Function Add(ByVal x As Integer, ByVal у As Integer) As Integer
Return x + у
End Function
End Class
End Namespace
Если теперь проверить CIL-код для метода Add(), вы обнаружите подобные инструкции (слегка "подправленные" компилятором VB .NET).
.method public instance int32 Add(int32 x, int32 y) cil managed
{
// Code size 9 (0x9)
.maxstack 2
.locals init ([0] int32 Add)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add.ovf
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method Calc::Add
Преимущества CIL
Вы можете спросить, зачем компилировать исходный код в CIL, а не прямо в набор специальных системных команд. Одним из преимуществ этого является интеграция языков, поскольку вы уже убедились, что все компиляторы .NET выдают приблизительно одинаковые наборы CIL-инструкций. Поэтому все языки могут взаимодействовать в рамках четко обозначенной двоичной "арены".
Кроме того, поскольку CIL демонстрирует независимость от платформы, каркас .NET Framework тоже оказывается независимым от платформы, обеспечивая то, к чему так привыкли разработчики Java (единую базу программного кода, способного работать во многих операционных системах). Фактически уже имеется международный стандарт для языка C#, а значительная часть платформы .NET реализована для множества операционных систем, отличных от Windows (более подробная информация об этом имеется в конце главы). Но, в отличие от Java, .NET позволяет строить приложения, используя язык вашего предпочтения.
Преобразование CIL-кода в набор инструкций, соответствующих платформе
Ввиду того, что компоновочные блоки содержат CIL-инструкции, а не инструкции для конкретной платформы, программный код CIL перед использованием приходится в фоновом режиме компилировать. Объект, который компилирует программный код CIL в инструкции, понятные процессору машины, называется JIT-компилятором (just-in-time – точно к нужному моменту), который иногда "по-дружески" также называют Jitter. Среда выполнения .NET использует JIT-компилятор, соответствующий конкретному процессору и оптимизированный для соответствующей платформы.
Например, если ваше .NET-приложение предназначено для выполнения на "компактном" устройстве (таком, как, например, КПК), то соответствующий JIT-компилятор будет иметь специальные средства для учета условий ограниченности памяти. Если это компоновочный блок для серверной системы (где объем памяти редко оказывается проблемой), то соответствующий JIT-компилятор будет оптимизирован для работы в условиях достаточного объема памяти. Таким образом разработчики получают возможность создавать только один блок программного кода, который с помощью JIT-компиляции можно выполнять на машинах с разной архитектурой.
К тому же, при компиляции CIL-инструкций в соответствующий машинный код JIT-компилятор поместит результаты компиляции в кэш в соответствии с тем, как этого требует соответствующая операционная система. Так, при первом вызове метода с именем PrintDocument() соответствующие CIL-инструкции компилируются в конкретные инструкции платформы и сохраняются в памяти для использования в дальнейшем. Поэтому при следующих вызовах PrintDocument () необходимости в повторной компиляции CIL не возникает.
Роль метаданных типов .NET
Кроме CIL-инструкций, компоновочный блок .NET содержит исчерпывающие и точные метаданные, описывающие все его типы (классы, структуры, перечни и т.д.), определенные в бинарном объекте, и все члены каждого типа (свойства, методы, события и т.д.). К счастью, задача создания метаданных всегда возлагается на компилятор (а не на программиста). По причине того, что метаданные .NET так подробны и точны, компоновочные блоки оказываются единицами, способными себя полностью описать, – настолько полно, что для бинарных .NET-объектов не возникает необходимости регистрироваться в реестре системы.
Для иллюстрации формата метаданных типов .NET давайте рассмотрим метаданные, сгенерированные для метода Add() C#-класса Calc, представленного выше (метаданные, генерируемые для VB .NET-версии метода Add(), оказываются аналогичными).
TypeDef #2 (02000003)
-----------------------------------------------------------
TypDefName: CalculatorExample.Calc (02000003)
Flags: [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldlnit] (00100001)
Extends: 01000001 [TypeRef] System.Object
Method #1 (06000003)
-----------------------------------------------------------
MethodName: Add (06000003)
Flags: [Public] [HideBySig] [ReuseSlot] (00000086)
RVA: 0x00002090
ImplFlags: [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
2 Arguments
Argument #1: I4
Argument #2: I4
2 Parameters
(1) ParamToken: (08000001) Name: x flags: [none] (00000000)
(2) ParamToken: (08000002) Name: у flags: [none] (00000000)
Метаданные используются средой выполнения .NET, а также различными средствами разработки. Например, возможность IntelliSense, предлагаемая в Visual Studio 2005 в режиме проектирования, основана на чтении метаданных компоновочного блока. Метаданные используются различными утилитами просмотра объектов, инструментами отладки и самим компилятором C#. Для полноты картины заметим также, что использование метаданных лежит в основе множества .NET-технологий, включая удаленный доступ, отображение типов, динамическое связывание, Web-сервисы XML и сериализацию объектов.
Роль манифеста компоновочного блока
Наконец вспомним, что компоновочный блок .NET содержит также метаданные, описывающие сам компоновочный блок (эти метаданные называются манифест). Среди всего прочего, в манифесте документируются все внешние компоновочные блоки, которые требуются текущему компоновочному блоку для корректного функционирования, указан номер версии компоновочного блока, информация об авторских правах и т.д. Подобно метаданным типов, генерирование манифеста компоновочного блока тоже является задачей компилятора. Вот некоторые подходящие для иллюстрации элементы манифеста CSharpCalculator.exe.