Рабочим названием платформы .NET было |
Верификация CIL-кода. Библиотеки для создания метаинструментов
Библиотеки для создания метаинструментов
Во второй главе мы рассмотрели написанную на языке 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.
Работа с 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, которые условно можно разделить на три основные группы:
- Методы 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.
- Методы FindXXX предназначены для поиска элементов метаданных, удовлетворяющих некоторым критериям. Например, поиск типа по его имени ("MyType1") проводится следующим образом:
mdTypeDef token; mdimp->FindTypeByName(L"MyType1",NULL,&token);
- Методы GetXXX используются для получения свойств элементов метаданных. Например, получение содержимого строки, токен которой хранится в переменной strToken, выглядит так:
unsigned short s[1024]; unsigned long len; mdimp->GetUserString(strToken,s,1024,&len);
Обратите внимание, что для хранения одного символа применяется тип unsigned short. Причина в том, что для представления строковых данных в .NET используется 16-разрядная кодировка Unicode.
Генерация метаданных осуществляется через методы интерфейса IMetadataEmit, которые можно условно разделить на две группы:
- Методы DefineXXX добавляют новые элементы метаданных;
- Методы SetXXX устанавливают свойства элементов метаданных.
Сгенерированные метаданные могут быть сохранены на диске при помощи метода Save.