Опубликован: 28.06.2006 | Уровень: специалист | Доступ: платный | ВУЗ: Московский государственный технический университет им. Н.Э. Баумана
Лекция 9:

Верификация CIL-кода. Библиотеки для создания метаинструментов

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >

Библиотеки для создания метаинструментов

Во второй главе мы рассмотрели написанную на языке C программу, осуществлявшую генерацию сборки .NET. Эта программа не использовала никаких библиотек для работы с метаданными и CIL-кодом, то есть задача генерации сборки была решена на самом низком уровне - на уровне двоичных форматов. Хотя такой подход во многих случаях бывает оправдан, на практике гораздо удобнее воспользоваться специальными библиотеками, предназначенными для разработчиков метаинструментов.

Напомним, что метаинструменты для платформы .NET - это программы, рассматривающие сборки .NET в качестве объектов анализа, генерации или преобразования. При этом анализ существующей сборки выполняется верификаторами, загрузчиками, отладчиками и т.д. Генерация новой сборки осуществляется компиляторами и RAD-средствами. Преобразование сборки выполняется оптимизаторами и средствами, затрудняющими декомпиляцию и взлом сборки.

Сборка .NET представляет собой совокупность метаданных, CIL-кода и, возможно, ресурсов. Поэтому библиотеки, необходимые для создания метаинструментов, должны обеспечивать чтение и генерацию этой информации. В составе .NET Framework SDK поставляются две библиотеки, частично решающие эти задачи. Это Metadata Unmanaged API и библиотека рефлексии (Reflection API). Кроме того, существуют созданные сторонними разработчиками библиотеки AbsIL SDK и Reflection Extension API. В данном разделе проведем обзор этих библиотек.

Metadata Unmanaged API

Metadata Unmanaged API осуществляет импорт и генерацию метаданных. Это API, как явствует из его названия, работает не под управлением .NET Runtime. Оно предназначено, главным образом, для использования в компиляторах и загрузчиках, которым требуется высокая скорость доступа к метаданным и работа с метаданными на низком уровне.

При использовании Metadata Unmanaged API метаинструмент работает с образом сборки в памяти (в документации такой образ называется scope). Метаинструмент может создать образ новой сборки или загрузить существующую сборку из файла, а кроме того, может сохранить образ сборки в файл. Навигация через иерархию метаданных осуществляется на достаточно низком уровне с использованием токенов метаданных (напомним, что токен некоторого элемента метаданных - это 32-разрядное число, старший байт которого обозначает таблицу, в которой хранятся элементы метаданных соответствующего типа, а остальные три байта являются индексом элемента в этой таблице). Metadata Unmanaged API предоставляет прямой доступ к таблицам метаданных, позволяет сливать несколько сборок в одну, но не содержит явных средств для работы с CIL-кодом. Кроме того, метаинструмент, использующий это API, должен быть написан на C++.

Для того чтобы использовать Metadata Unmanaged API из программы, написанной на Visual C++, необходимо включить в программу следующие строки:

#include <corhlpr.h>
#pragma comment(lib, "format.lib")

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

Взаимодействие с Metadata Unmanaged API осуществляется через набор COM-интерфейсов. Они перечислены в таблице 4.2.

Таблица 4.2. Интерфейсы Metadata Unmanaged API
Интерфейс Описание
IMetadataDispenserEx Позволяет загружать образ исполняемого либо объектного файла в память или создавать новый образ
IMetadataImport Предназначен для чтения метаданных
IMetadataEmit Предназначен для генерации метаданных

Работа с Metadata Unmanaged API начинается с инициализации системы COM и получения указателя на интерфейс IMetadataDispenserEx:

CoInitialize(NULL);
IMetaDataDispenser *dispenser;
HRESULT h = CoCreateInstance(
 CLSID_CorMetaDataDispenser,
 NULL,
 CLSCTX_INPROC_SERVER, 
 IID_IMetaDataDispenserEx,
 (void **)&dispenser
 );
if (h)
 printf("Error");

Затем можно с помощью метода OpenScope этого интерфейса загрузить существующую сборку в память или с помощью метода DefineScope создать новую сборку. Оба метода возвращают указатель на интерфейс, через который можно в дальнейшем осуществлять чтение или генерацию метаданных. В следующем фрагменте программы происходит вызов метода OpenScope для чтения метаданных из сборки test.exe:

IMetaDataImport *mdimp;
HRESULT h = dispenser->OpenScope(
 L"test.exe",
 0,
 IID_IMetaDataImport,
 (IUnknown**)&mdimp
 );
if (h)
 printf("Error");

При завершении работы с Metadata Unmanaged API необходимо освободить указатели на полученные интерфейсы:

mdimp->Release();
dispenser->Release();

Чтение метаданных из сборки осуществляется через методы интерфейса IMetadataImport, которые условно можно разделить на три основные группы:

  1. Методы EnumXXX возвращают массивы токенов, описывающих определенную категорию элементов метаданных. Ответственность за выделение достаточного количества памяти для хранения возвращаемого массива токенов лежит на программисте, использующем библиотеку, а так как количество токенов заранее неизвестно, то приходится использовать прием, проиллюстрированный следующим примером. В нем мы получаем массив токенов, который соответствует типам, объявленным в сборке (другими словами, получаем содержимое таблицы TypeDef):
    mdTypeDef tmp;
    mdTypeDef *tokens;
    HCORENUM Enum = 0;
    unsigned long tokenCount;
    mdimp->EnumTypeDefs(&Enum,&tmp,1,&tokenCount);
    mdimp->CountEnum(Enum,&tokenCount);
    if (tokenCount > 0)
    {
      tokens = new mdTypeDef [tokenCount];
      tokens[0] = tmp;
      if (tokenCount > 1)
    	mdimp->EnumTypeDefs(
    	&Enum,tokens+1,tokenCount-1,
    	&tokenCount
    	);
      mdimp->CloseEnum(Enum);
    }

    Первый вызов метода EnumTypeDefs создает дескриптор массива токенов (он записывается в переменную Enum ), а также возвращает первый элемент этого массива (он сохраняется в переменной tmp ).

    Затем метод CountEnum записывает в переменную tokenCount размер массива токенов, после чего выделяется нужное количество памяти (для массива tokens ) и второй раз вызывается метод EnumTypeDefs. Обратите внимание, что первому элементу массива tokens мы присваиваем значение переменной tmp.

  2. Методы FindXXX предназначены для поиска элементов метаданных, удовлетворяющих некоторым критериям. Например, поиск типа по его имени ("MyType1") проводится следующим образом:
    mdTypeDef token;
    mdimp->FindTypeByName(L"MyType1",NULL,&token);
  3. Методы GetXXX используются для получения свойств элементов метаданных. Например, получение содержимого строки, токен которой хранится в переменной strToken, выглядит так:
    unsigned short s[1024];
    unsigned long len;
    mdimp->GetUserString(strToken,s,1024,&len);

    Обратите внимание, что для хранения одного символа применяется тип unsigned short. Причина в том, что для представления строковых данных в .NET используется 16-разрядная кодировка Unicode.

Генерация метаданных осуществляется через методы интерфейса IMetadataEmit, которые можно условно разделить на две группы:

  1. Методы DefineXXX добавляют новые элементы метаданных;
  2. Методы SetXXX устанавливают свойства элементов метаданных.

Сгенерированные метаданные могут быть сохранены на диске при помощи метода Save.

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >
Анастасия Булинкова
Анастасия Булинкова
Рабочим названием платформы .NET было
Bogdan Drumov
Bogdan Drumov
Молдова, Республика
Azamat Nurmanbetov
Azamat Nurmanbetov
Киргизия, Bishkek