Type library
TLB (англ. Type Library — библиотека типов) — иерархическое хранилище информации о возможностях ActiveX-сервера в OLE Automation.
Библиотека типов — одно из ключевых понятий технологии OLE Automation. Библиотека типов представляет собой иерархическое (трёхуровневое, считая корневой элемент) хранилище информации о возможностях ActiveX-сервера. Чаще всего библиотека типов хранится либо как отдельный файл с расширением «.tlb» или «.olb», либо внутри (в ресурсах) ActiveX-компонента[1], который она описывает. Кроме того, библиотека типов может находиться в составном документе OLE.
Причины создания
С появлением технологии ActiveX возникла необходимость в наличии единого рекомендованного способа получения подробных сведений об ActiveX-компоненте: списка реализуемых им классов и поддерживаемых интерфейсов, их идентификаторов, кратких описаний и прочего. С этой целью и были введены библиотеки типов.
Логическая структура
Библиотека типов является трёхуровневым иерархическим хранилищем: вершиной иерархии является сама библиотека (англ. Type Library), представляющая собой набор типоописаний (англ. Type Info), являющихся, в свою очередь, контейнерами элементов третьего уровня — членов (англ. Member).
Все три типа элементов имеют одинаковый набор базовых характеристик:
- имя (англ. Name),
- краткое описание (англ. Documentation String),
- файл справки (hlp или chm) и идентификатор справочной статьи (англ. Help File Name и англ. Help Context ID).
Кроме того, библиотека и типоописания имеют уникальные 128-битные идентификаторы, а члены — 32-битные (уникальные в пределах одного типоописания). Идентификатор библиотеки называется LIBID, члена — MEMBERID. Название идентификатора типоописания зависит от вида типоописания.
В библиотеке типов могут описываться сущности восьми различных видов. Каждое типоописание определяет одну из них. В соответствии с этим, атрибутом типоописания, имеющим первостепенную важность при разборе типоописания, является вид типоописания (англ. Type Kind). Этот атрибут устанавливает вид сущности, описываемой данным типоописанием, и, тем самым, задаёт способ интерпретации всех прочих параметров и подчинённых элементов типоописания.
Следующая таблица показывает возможные виды сущностей:
Сущность | Члены | Type Kind | Название идентификатора |
---|---|---|---|
Перечисление (Enum) | Константы | TKIND_ENUM | — |
Объединение (Union) | Поля объединения | TKIND_UNION | — |
Структура[2] | Поля структуры | TKIND_RECORD | — |
COM-интерфейс | Методы, свойства, поля класса, события |
TKIND_INTERFACE | IID (Interface ID) |
Disp-интерфейс[3] | TKIND_DISPATCH | ||
COM-класс | Поддерживаемые COM-интерфейсы | TKIND_COCLASS | CLSID (Class ID) |
Псевдоним | — | TKIND_ALIAS | — |
Обычный модуль | Функции, свойства, переменные |
TKIND_MODULE | — |
Значение
TLB содержит ряд важной информации, необходимой как при разработке, так и в процессе работы приложений.
- Описание метода интерфейса в числе прочего содержит:
- Смещение ячейки данного метода в VTable. Компилятор может сгенерировать код, осуществляющий вызов метода по типу раннего связывания, только если ему известно смещение.
- DispId (для Disp-интерфейсов) — особый числовой идентификатор метода. Компилятор может сгенерировать код, осуществляющий вызов метода по типу позднего связывания по DispId’у, только если ему известно значение этого идентификатора. В противном случае, возможно только позднее связывание по имени метода.
- Соглашение о вызове. При использовании раннего связывания генерация осуществляющего вызов кода принципиально невозможна, если не известно используемое соглашение.
- Описание функции из модуля содержит информацию о динамической библиотеке, экспортирующей данную функцию, её (функции) экспортное имя и/или ординал. Компилятор может сгенерировать корректную таблицу импорта, только если он обладает всеми этими сведениями.
- Описания интерфейсов и классов содержат информацию об их уникальных идентификаторах (IID и CLSID соответственно). Программа не может запросить у OLE создание объекта требуемого класса, если не известен его CLSID, или запросить у объекта требуемый интерфейс, если не известен его IID.
- Описания членов интерфейсов и модулей содержат информацию о количестве и типах параметров, о типах возврата (то есть прототипы), что позволяет компилятору проверить правильность вызовов.
- Библиотека типов содержит информацию, необходимую для осуществления маршалинга.
- При поддержке средой разработки, ощутимую помощь разработчику оказывают краткие описания элементов библиотеки. Кроме того, разработчик может «встать» кареткой на интересующий идентификатор и (обычно нажатием F1) получить подробную справочную информацию по нему, благодаря тому, что для каждого элемента может храниться ссылка на файл справки и соответствующую справочную статью.
- Технологии автодополнения, такие как IntelliSense, могут использовать библиотеки типов как источник информации.
- Во время работы программы, имея ссылку на неизвестный объект, можно получить о нём почти всю информацию, при условии, что он поддерживает интерфейс ITypeInfo. В этом случае чаще всего объект, чтобы предоставить информацию о себе, использует ITypeInfo, полученный в результате загрузки своей же TLB[4].
C точки зрения хранимых сведений, библиотека является более продвинутым[5] аналогом заголовочных файлов.
Использование
Программное
OLE API предлагает функции[6], позволяющие загрузить библиотеку типов и работать с ней через интерфейсы ITypeLib и ITypeLib2, а с хранящимися в ней сущностями — через ITypeInfo и ITypeInfo2.
Microsoft Visual Basic
Для Visual Basic поддержка TLB является естественной и неотъемлемой, так как это единственный механизм, позволяющий привнести в пространство имён проекта информацию об уже существующих интерфейсах, классах, типах: язык позволяет объявлять свои, новые интерфейсы и классы, но не уже существующие[7]. Так, например, большинство «встроенных» функций, типов, классов и интерфейсов языка объявлены в соответствующих библиотеках типов.
Библиотека типов подключается к проекту через Project→References (или косвенно, через Project→Components). Несколько «базовых» библиотек подключены изначально и не поддаются отключению.
Microsoft Visual C++
MSVC++ дополнен специальной директивой препроцессора #import, создающей для подключаемой библиотеки типов отдельное пространство имён[8], а для каждой описанной в библиотеке сущности — соответствующее С++-совместимое объявление.
Пример:
// Импорт библиотеки типов по имени tlb-файла
#import "../tlb/foobar.tlb"
// Импорт библиотеки типов из ресурсов PE-файла
#import "winhttp.dll"
// Импорт библиотеки по её LIBID'у
#import "libid:12341234-1234-1234-1234-123412341234"
Borland Delphi и Borland C++ Builder
В этих средах разработки имеется мастер импорта компонентов (англ. Import Component Wizard), доступный через меню Component→Import Component, позволяющий сгенерировать на основе библиотеки типов соответствующий pas- или h-файл с объявлениями.
PHP
В PHP имеется функция com_load_typelib(), которая загружает библиотеку типов и регистрирует в пространстве имён PHP константы из этой библиотеки. Функция com_print_typeinfo() выводит адаптированный дамп типоописания указанного класса/интерфейса.
Создание
Программное
OLE API предоставляет функции CreateTypeLib, CreateTypeLib2, интерфейсы ICreateTypeLib, ICreateTypeLib2, ICreateTypeInfo, ICreateTypeInfo2, с помощью которых задача создания и сохранения произвольной библиотеки типов решается программно. Разработчики могут создавать свои собственные приложения для создания библиотек типов, пользуясь этими API-функциями и интерфейсами.
Ручное
Для разработки библиотек типов Microsoft создала специальный язык MIDL. Исходные файлы, написанные на этом языке, компилируются с помощью одноимённой утилиты-компилятора midl.exe. Раньше для этих целей использовался mktyplib.exe, который в данное время считается устаревшим и не рекомендован к использованию. Существует значительная разница между требованиями этих двух утилит к входным данным: далеко не всякий код, верный для первого, будет верен для второго, и наоборот[9].
[
odl,
uuid(D8F015C0-C278-11CE-A49E-444553540000),
helpstring("Definition of interface IShellDispatch"),
hidden,
dual,
oleautomation
]
interface IShellDispatch : IDispatch {
[id(0x60020000), propget, helpstring("Get Application object")]
HRESULT Application([out, retval] IDispatch** ppid);
[id(0x60020001), propget, helpstring("Get Parent object")]
HRESULT Parent([out, retval] IDispatch** ppid);
[id(0x60020002), helpstring("Get special folder from ShellSpecialFolderConstants")]
HRESULT NameSpace(
[in] VARIANT vDir,
[out, retval] Folder** ppsdf);
[id(0x60020003), helpstring("Browse the name space for a Folder")]
HRESULT BrowseForFolder(
[in] long Hwnd,
[in] BSTR Title,
[in] long Options,
[in, optional] VARIANT RootFolder,
[out, retval] Folder** ppsdf);
[id(0x60020004), helpstring("The collection of open folder windows")]
HRESULT Windows([out, retval] IDispatch** ppid);
[id(0x60020005), helpstring("Open a folder")]
HRESULT Open([in] VARIANT vDir);
[id(0x60020006), helpstring("Explore a folder")]
HRESULT Explore([in] VARIANT vDir);
[id(0x60020007), helpstring("Minimize all windows")]
HRESULT MinimizeAll();
[id(0x60020008), helpstring("Undo Minimize All")]
HRESULT UndoMinimizeALL();
[id(0x60020009), helpstring("Bring up the file run")]
HRESULT FileRun();
[id(0x6002000a), helpstring("Cascade Windows")]
HRESULT CascadeWindows();
[id(0x6002000b), helpstring("Tile windows vertically")]
HRESULT TileVertically();
[id(0x6002000c), helpstring("Tile windows horizontally")]
HRESULT TileHorizontally();
[id(0x6002000d), helpstring("Exit Windows")]
HRESULT ShutdownWindows();
[id(0x6002000e), helpstring("Suspend the pc")]
HRESULT Suspend();
[id(0x6002000f), helpstring("Eject the pc")]
HRESULT EjectPC();
[id(0x60020010), helpstring("Bring up the Set time dialog")]
HRESULT SetTime();
[id(0x60020011), helpstring("Handle Tray properties")]
HRESULT TrayProperties();
[id(0x60020012), helpstring("Display shell help")]
HRESULT Help();
[id(0x60020013), helpstring("Find Files")]
HRESULT FindFiles();
[id(0x60020014), helpstring("Find a computer")]
HRESULT FindComputer();
[id(0x60020015), helpstring("Refresh the menu")]
HRESULT RefreshMenu();
[id(0x60020016), helpstring("Run a Control Panel Item")]
HRESULT ControlPanelItem([in] BSTR szDir);
Расширяемость
Формат файла библиотеки типов предусматривают возможность расширения набора хранимых в библиотеке сведений. Для того, чтобы сохранить в библиотеке некоторую информацию, хранение которой изначально не предусмотрено, существует возможность снабдить любой элемент иерархии (библиотеку, типоописание, член), а также параметр метода/функции блоком произвольных данных (англ. Custom Data).
Microsoft не устанавливает какого-либо формата блока произвольных данных — это могут быть любые данные, способные быть сохранёнными в Variant-переменной. При таком подходе возможны ситуации, когда разные разработчики будут использовать эту возможность для различных целей, что привело бы к многочисленным проблемам несовместимости. Чтобы подобных коллизий не возникало, при сохранении и получении произвольных данных необходимо указывать уникальный идентификатор (GUID), который однозначно определяет смысл блока произвольных данных и, как следствие, его формат.
Таким образом, любой элемент может быть снабжён не одним, а несколькими блоками произвольных данных, причём разные блоки могут быть оставлены разными программами и служить совершенно разным целям.
На практике, библиотеки типов, в которых бы использовалась эта возможность, встречаются крайне редко.
Прочее
Утилиты
Следующие утилиты могут быть использованы при работе с библиотеками типов:
- MIDL (конс.)
Компилятор языка MIDL, создаёт TLB-файл на основе исходного кода. - MkTypLib (конс.)
Устаревший и не рекомендованный к использованию компилятор для создания TLB-файлов. Синтаксис входных файлов отличается от оного у MIDL’а. При необходимости компиляции исходников, написанных в «старом стиле», рекомендуется использовать MIDL с ключом /mktyplib203[11]. - regtlib (конс.)
Утилита для регистрации библиотек типов в реестре. - regsvr32 (оконн.)
Утилита для регистрации ActiveX-серверов в реестре. При регистрации обычно косвенным образом регистрируется и TLB, хранящаяся в ресурсах ActiveX-сервера. - Microsoft OLE View (оконн.)
Утилита, отображающая список всех зарегистрированных в системе библиотек типов, позволяющая также просматривать содержимое любой из них. Способна восстанавливать исходный код просматриваемого элемента (всей библиотеки/типоописания/члена). Поставляется вместе с Microsoft Visual Studio 6.0. - Обозреватель объектов (англ. Object Browser) в Visual Basic есть не что иное, как обозреватель подключенных к проекту библиотек типов.
Нецелевое использование
При разработке в среде Visual Basic библиотеки типов нередко используются не по своему прямому назначению, а для осуществления раннего[12] импорта обычных WinAPI-функций[13], как единственно возможный в VB вариант осуществления импорта через таблицу импорта.
...
typedef struct tagCOMBOBOXEXITEMA
{
ComboBoxExItemMaskFlags mask;
int iItem;
int pszText;
int cchTextMax;
int iImage;
int iSelectedImage;
int iOverlay;
int iIndent;
LPARAM lParam;
} COMBOBOXEXITEMA, *PCOMBOBOXEXITEMA;
[dllname("comctl32.dll")]
module ComCtl
{
[entry("InitCommonControlsEx")]
int InitCommonControlsEx(LPINITCOMMONCONTROLSEX lpInitCtrls);
[entry("ImageList_ReplaceIcon")]
int ImageList_ReplaceIcon(HIMAGELIST himl, int i, HICON hicon);
}
[dllname("user32.dll")]
module IconsAPI
{
[entry("LoadImageW")]
int LoadImage(int hinst, void* lpszName, int uType, int cxDesired, int cyDesired, int fuLoad);
}
...
Примечания
- Который, в свою очередь, является PE-файлом и имеет расширение «.dll», «.ocx», «.cpl», «.exe» и т. д.
- По терминологии C/C++ — структура (struct), по терминологии Паскаля — запись (record), по терминологии Visual Basic — пользовательский тип (User-Defined Type, UDT).
- COM-интерфейс, унаследованный от IDispatch, обеспечивающий поддержку позднего связывания с помощью IDispatch::Invoke.
- Загрузка осуществляются силами OLE Automation, а не разработчика.
- Поскольку хранит в себе гораздо больше полезной информации, компактнее и быстрее (не нужно делать парсинг) заголовочных файлов и, главное, может использоваться в любой среде разработки и любом языке программирования, которые поддерживает COM, а не только в C/C++.
- LoadTypeLib и LoadTypeLibEx
- Ограничение заключается в невозможности указать IID/CLSID для объявляемого интерфейса/класса — идентификатор присваивается автоматически, что делает объявленные сущности принципиально несовместимыми с существующими.
- Это поведение отключается модификатором no_namespace. Подробнее см. в MSDN
- Differences Between MIDL and MkTypLib (MSDN).
- Код получен в результате декомпиляции TLB утилитой Microsoft OLE/COM Object Viewer.
- Описание ключа /mktyplib203 (англ., MSDN).
- В противоположность позднему — с помощью Declare Sub/Function.
- В общем случае, никак не связанных с COM и, тем более, с OLE Automation.