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

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

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

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

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

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

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

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

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

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

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

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

  • Атрибуты.

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

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

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

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

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

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

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

  • Атрибуты.

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

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

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

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

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

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

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

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

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

0x06000004

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

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

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

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

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

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

Для лучшего понимания метаданных и их роли в среде 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);
   }
}

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

В следующем примере показана часть CIL, созданная из функции предыдущего кода Main . CIL и метаданные можно просмотреть из любого приложения .NET с помощью дизассемблера CIL (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 считывает CIL для всего метода, тщательно анализирует его и создает эффективные собственные инструкции для метода. В строке 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 позволяет среде выполнения вычислять начальный адрес памяти CIL, который определяет этот метод. Столбцы Неявные флаги и Флаги содержат битовые маски, описывающие метод (например, является ли метод общим или закрытым). Столбец Имя содержит указатель имени метода из кучи строк. Столбец Сигнатура содержит указатель определения сигнатуры метода в куче больших двоичных объектов.

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

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