Метаданные и компоненты с самоописанием

В прошлом программный компонент (EXE или DLL), написанный на одном языке, не мог просто так использовать программный компонент, написанный на другом языке. Модель COM стала шагом вперед в решении этой проблемы. Платформа .NET упрощает взаимодействие компонентов, позволяя компиляторам добавлять дополнительные описательные данные во все модули и сборки. Эти данные, называемые метаданными, способствуют эффективному взаимодействию компонентов.

Метаданные — это данные в двоичном формате с описанием программы, хранящиеся либо в переносимом исполняемом (PE) файле среды CLR, либо в памяти. Когда код компилируется в PE-файл, метаданные помещаются в одну часть файла, а код преобразуется в MSIL и помещается в другую часть файла. В метаданных описываются все типы и члены, определенные или используемые в модуле или сборке. При исполнении кода среда выполнения загружает метаданные в память и обращается к ним для получения сведений о классах, членах, наследовании и других элементах кода.

В метаданных в независимом от языка виде описываются все типы и члены, определенные в коде. В метаданных хранятся следующие сведения.

  • Описание сборки.

    • Удостоверение (имя, версия, язык и региональные параметры, открытый ключ).

    • Экспортируемые типы.

    • Другие сборки, от которых зависит данная сборка.

    • Необходимые разрешения безопасности.

  • Описание типов.

    • Имя, видимость, базовый класс и реализованные интерфейсы.

    • Члены (методы, поля, свойства, события, вложенные типы).

  • Атрибуты.

    • Дополнительные описательные элементы, изменяющие типы и члены.

Преимущества метаданных

Метаданные — это ключ к более простой модели программирования; они устраняют необходимость в файлах IDL, файлах заголовков или каких-либо внешних методах ссылки на компоненты. Метаданные позволяют языкам платформы .NET автоматически описывать себя не зависящим от языка образом незаметно для разработчика и пользователя. К тому же метаданные имеют возможности для расширения за счет использование атрибутов. Метаданные обеспечивают также следующие преимущества.

  • Файлы с самоописанием.

    Модули среды CLR и сборки обладают свойством самоописания. Метаданные модуля содержат все необходимое для взаимодействия с другим модулем. Метаданные автоматически предоставляют функциональные возможности IDL для модели СОМ, в связи с чем один и тот же файл может использоваться и для определения, и для реализации. Модули и сборки среды выполнения даже не требуют регистрации в операционной системе. Благодаря этому описания, используемые средой выполнения, всегда отражают фактический код в скомпилированном файле, что повышает надежность приложения.

  • Взаимодействие языков и упрощение разработки на основе компонентов

    Метаданные содержат полные сведения о скомпилированном коде, необходимые для наследования класса из PE-файла, написанного на другом языке. Вы можете создать экземпляр любого класса, написанного на любом управляемом языке (любой язык, предназначенный для среды CLR), не беспокоясь о явном маршаллинге или использовании пользовательского кода взаимодействия.

  • Атрибуты.

    Платформа .NET позволяет объявлять особые виды метаданных, называемые атрибутами, в скомпилированном файле. Атрибуты находятся в .NET и используются для более детального контроля над работой программы во время ее выполнения. Также с помощью атрибутов в файлы .NET можно вносить пользовательские метаданные, определяемые самим пользователем. Дополнительные сведения см. в разделе Атрибуты.

Метаданные и структура PE-файла

Метаданные хранятся в одном разделе переносимого исполняемого (PE) файла .NET, а код на промежуточном языке MSIL хранится в другом его разделе. Раздел файла с метаданными содержит ряд табличных структур и структур данных кучи. Раздел MSIL содержит код MSIL и лексемы метаданных, ссылающиеся на раздел метаданных PE-файла. При использовании таких средств, как дизассемблер MSIL (Ildasm.exe), для просмотра MSIL-кода можно обнаружить токены метаданных.

Таблицы и кучи метаданных

Каждая таблица метаданных содержит сведения об элементах программы. К примеру, в одной таблице метаданных описываются классы в коде, в другой — поля и так далее. Если в коде используется десять классов, таблица классов будет содержать десять строк, по одной для каждого класса. Таблицы метаданных ссылаются на другие таблицы и кучи. Например, таблица метаданных для классов ссылается на таблицу методов.

В метаданных сведения также хранятся в четырех структурах кучи — в куче строк, больших двоичных объектов, пользовательских строк и идентификаторов GUID. Все строки, используемые для названия типов и членов, хранятся в куче строк. Например, в таблице методов не хранится имя конкретного метода, но содержится ссылка на имя этого метода, находящееся в куче строк.

Лексемы метаданных

Каждая строка любой таблицы метаданных уникально идентифицируется в разделе MSIL PE-файла с помощью лексемы метаданных. Лексемы метаданных по своей сути похожи на указатели, сохраненные в MSIL, которые ссылаются на определенную таблицу метаданных.

Лексема метаданных является четырехбайтным числом. Старший байт указывает на таблицу метаданных, к которой относится данная лексема (метод, тип и т. д.). Остальные три байта определяют строку в таблице метаданных, которая соответствует описываемому программному элементу. Если определить метод в языке C# и скомпилировать его в PE-файл, то в разделе MSIL PE-файла может появиться следующая лексема метаданных:

0x06000004

Старший байт (0x06) указывает, что это лексема MethodDef. Три младших байта (000004) отсылают среду CLR к четвертой строке таблицы MethodDef для получения сведений с описанием определения метода.

Метаданные внутри PE-файла

После компиляции программы для среды CLR она преобразуется в PE-файл, состоящий из трех разделов. В следующей таблице описано содержание каждого раздела.

Раздел PE-файла Содержание раздела PE-файла
Заголовок PE Индекс основных разделов PE-файла и адрес точки входа.

Среда выполнения использует эти сведения для определения файла как PE-файла и определения начала выполнения при загрузке программы в память.
Инструкции MSIL Инструкции промежуточного языка MSIL, реализующие пользовательский код. Многие инструкции MSIL сопровождаются лексемами метаданных.
Метаданные Таблицы и кучи метаданных Среда выполнения использует этот раздел для записи сведений о каждом типе и члене в коде. Этот раздел также содержит пользовательские атрибуты и сведения о безопасности.

Использование метаданных во время выполнения

Для лучшего понимания метаданных и их роли в среде CLR полезно написать простую программу и наглядно проиллюстрировать, как метаданные влияют на работу среды выполнения. В приведенном ниже примере показано два метода внутри класса MyApp. Метод Main является точкой входа программы, а метод Add — просто возвращает сумму двух целочисленных аргументов.

Public Class MyApp
   Public Shared Sub Main()
      Dim ValueOne As Integer = 10
      Dim ValueTwo As Integer = 20
      Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo))
   End Sub

   Public Shared Function Add(One As Integer, Two As Integer) As Integer
      Return (One + Two)
   End Function
End Class
using System;
public class MyApp
{
   public static int Main()
   {
      int ValueOne = 10;
      int ValueTwo = 20;
      Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
      return 0;
   }
   public static int Add(int One, int Two)
   {
      return (One + Two);
   }
}

При запуске кода среда выполнения загружает модуль в память и обращается к метаданным за сведениями об этом классе. После загрузки среда выполнения проводит подробный анализ потока кода MSIL для метода, чтобы преобразовать его в инструкции машинного кода (выполнение которых осуществляется максимально быстро). В среде выполнения для преобразования инструкций MSIL в исходный машинный код используется JIT-компилятор. Преобразование выполняется по одному методу за раз по мере необходимости.

В приведенном ниже примере показана часть кода MSIL, полученного для функции Main из предыдущего кода. Код MSIL и метаданные любого приложения .NET можно просмотреть при помощи дизассемблера MSIL (Ildasm.exe).

.entrypoint
.maxstack  3
.locals ([0] int32 ValueOne,
         [1] int32 ValueTwo,
         [2] int32 V_2,
         [3] int32 V_3)
IL_0000:  ldc.i4.s   10
IL_0002:  stloc.0
IL_0003:  ldc.i4.s   20
IL_0005:  stloc.1
IL_0006:  ldstr      "The Value is: {0}"
IL_000b:  ldloc.0
IL_000c:  ldloc.1
IL_000d:  call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */

JIT-компилятор считывает код MSIL для всего метода, проводит тщательный анализ и создает эффективный набор машинных инструкций для этого метода. В строке IL_000d появляется лексема метаданных для метода Add (/* 06000003 */) и среда выполнения использует эту лексему для обращения к третьей строке таблице MethodDef.

В приведенной ниже таблице показана часть таблицы MethodDef, на которую ссылается лексема метаданных, описывающая метод Add. В сборке существуют и другие таблицы метаданных со своими уникальными значениями, но в данный момент рассматривается только эта таблица.

Строка Относительный виртуальный адрес (RVA) Неявные флаги Флаги Имя.

(Указывает на кучу строк.)
Сигнатура (указывает на кучу больших двоичных объектов)
1 0x00002050 IL

Управляется
Общедоступный

ReuseSlot

SpecialName

RTSpecialName

.ctor
.ctor (конструктор)
2 0x00002058 IL

Управляется
Общедоступный

Статические

ReuseSlot
Главная Строка
3 0x0000208c IL

Управляется
Общедоступный

Статические

ReuseSlot
Добавить int, int, int

В каждом столбце таблицы содержатся важные сведения о коде. Значения в столбце RVA позволяют среде выполнения вычислить начальный адрес в памяти кода MSIL, определяющего этот метод. Столбцы Неявные флаги и Флаги содержат битовые маски, описывающие метод (например, является ли метод общим или закрытым). Столбец Имя содержит указатель имени метода из кучи строк. Столбец Сигнатура содержит указатель определения сигнатуры метода в куче больших двоичных объектов.

Среда выполнения вычисляет нужный относительный адрес из столбца RVA в третьей строке и возвращает этот адрес JIT-компилятору, который затем переходит к новому адресу. JIT-компилятор продолжает обрабатывать код MSIL по новому адресу до тех пор, пока не найдет другую лексему метаданных, и затем процесс повторяется заново.

Благодаря метаданным среда выполнения имеет доступ к любым сведениям, необходимым для загрузки кода пользователя и его преобразования в инструкции машинного кода. Таким образом, метаданные позволяют использовать файлы с самоописанием и — совместно с системой общих типов — межъязыковое наследование.