Библиотека типов TLB может хранить в себе информацию о возможностях COM‐компонентов: классы, интерфейсы, методы, типы параметров и возвращаемые значения. В практических руководствах по программированию COM‐компонентов обычно рассказывают как создавать библиотеку типов вручную через комплилятор midl.exe, но сегодня рассмотрим как это делать программно через интерфейсы ICreateTypeLib2 и ICreateTypeInfo2. В качестве «языка программирования» будет выступать FreeBASIC. Во фрибейсике отсутствует ключевое слово Interface для объявления интерфейсов, но мы справимся с этой задачей и сами голыми руками. Для демонстрации серьёзности намерений построим интерфейс натуральной дроби и посмотрим на все этапы построения интерфейсов с нуля. Упростим понятие интерфейса до набора функций, которые следует обязательно реализовать. Определим класс натуральной дроби. У такого класса должны быть свойства, задающие числитель и знаменатель, а также мы добавим туда операцию сложения дробей. Так как нам впоследствии необходимо будет иметь доступ из унаследованного объекта к реализованным функциям, это значит, что такой набор должен состоять из указателей на наши будущие функции. Завернём их в структуру: Чтобы функции интерфейса знали, какой объект их вызывает, добавим в них первым параметром указатель на объект, реализующий наш интерфейс: На практике набор функций интерфейса выделяют в отдельную структуру, которую теперь называеют таблицей виртуальных методов, а в самом интерфейсе оставляют ссылку на неё. Название VirtualTable часто сокращают до Vtable или даже Vtbl: Пытаемся всё скомпилировать, но компилятор почему‐то сопротивляется такому коду. Дело в том, что виртуальная таблица IRationalVirtualTable ссылается на интерфейс IRational, объявленный позднее, а интерфейс IRational ссылается на виртуальную таблицу IRationalVirtualTable, и как их не меняй местами, ссылаться друг на друга от этого они не перестают. Выйти из ситуации поможет дополнительное имя для нашего интерфейса, введённое оператором Type, а к названию оригинального интерфейса добавим подчёркивание: Ну вот теперь‐то определение нашего интерфейса готово. Вся технология COM построена на интерфейсах. Все COM‐интерфейсы строятся по описанному выше принципу, но с дополнительными ограничениями: Для того, чтобы нашим интерфейсом можно было манипулировать не только через таблицу виртуальных функций, но и по именам функций из скриптовых языков программирования, рекомендуется наследовать интерфейсы не прямо от IUnknown, а от IDispatch. Интерфейсы, поддерживающие вызовы функций одновременно через таблицу и IDispatch, называются дуальными. Также сменим тип операндов функций с Integer на Long, чтобы соответствовать типам автоматизации. У каждого интерфейса и реализующего класса должны быть уникальные идентификаторы. Для этого используются 128‐битные числа, вычисляемые по специальному алгоритму, гарантирующему уникальность. Такие числа называют GUID. В заголовочном файле GUID определён в виде одноимённой структуры с дополнительными именами IID (идентификатор интерфейса) и CLSID (идентификатор класса). Получить GUID можно через утилиту guidgen.exe либо через функцию CoCreateGuid. В программе GUID для интерфейсов и классов записываются так: Изменим наш интерфейс в соответствии с этими требованиями, добавив заголовочные файлы и сторожей от повторного включения кода. Вот окончательный заголовочный файл IRational.bi: Наша библиотека типов будет состоять из определения интерфейса IRational и реализующего его класса Rational. Для работы среды COM её следует инициализировать вызовом CoInitialize(0). Когда она больше не нужна, вызываем соответствующий CoUnInitialize(). Определим идентификатор будущей библиотеки: Так как IRational наследуется от IDispatch, то нам необходимо загрузить библиотеку stdole32.tlb, где хранится ITypeInfo от IDispatch, чтобы потом добавить на него ссылку: Получить интерфейс ICreateTypeLib2 для создания библиотеки можно функцией CreateTypeLib2. Она принимает три параметра: тип системы (SYS_MAC, SYS_WIN16, SYS_WIN32 или SYS_WIN64), имя файла библиотеки и указатель на интерфейс. Метод CreateTypeInfo интерфейса ICreateTypeLib позволяет добавлять в библиотеку интерфейсы, классы, модули с функциями, перечисления, структуры, объединения и псевдонимы. Для этого ему нужно передать одно из значений перечисления TYPEKIND. Мы будем рассматривать добавление только классов и интерфейсов. Возвращаемым значением является ICreateTypeInfo для настройки полученной сущности. Для наследования необходимо создать ссылку на интерфейс‐папу. Небольшая палочка в колесо: придётся самостоятельно следить за индексами добавляемых ссылок на всех отцов. У функций есть параметры и возвращаемое значение. Функции представлены структурой FUNCDESC, параметры — массивом из структур ELEMDESC. Так как все функции интерфейса возвращают HRESULT, можно заранее создать возвращаемое значение для всех функций: Здесь vt определяет одно из значений перечисления VARENUM, а IDLFLAG_NONE указывает, что флагов не присвоено. Флаги могут принимать комбинацию из следующих значений: В некоторых случаях у объекта могут существовать свойства. Со стороны свойство выглядит как переменная, но изнутри обеспечивается с помощью функций установки и возврата значения. Функции можно описать одним из следующих значений: Ещё функции подразделаются по реализованности: В высокоуровневых языка программирования контексты вызова функций (указатель на объект), как и таблицы виртуальных функций, скрыты от программиста и не учитываются в интерфейсах, поэтому при настройке параметров мы не будем их указывать. Введём описание параметров функции GetNumerator: Заполнение структуры FUNCDESC для функции GetNumerator: Теперь функцию можно насаживать на интерфейс. Здесь опять придётся следить за индексом добавляемой функции, в нашем случае это 0: Похожим образом определяем вторую часть свойства Numerator: Для добавления функции в интерфейс опять придётся следить за индексами вручную. Однако в методе SetFuncAndParamNames необходимо указать индекс предыдущей функции GetNumerator, так как она является парной для свойства. Аналогичным образом добавляются функции для возвращения и установки знаменателя, с индексами 2 и 3. Функция AddRational принимает параметр типа указатель на IRational. Но в автоматизации такой тип данных отсутствует, поэтому заменяем его на IDispatch: В обозревателе объектов параметр будет виден как Object, а передача его будет идти по ссылке ByRef. Определение функции AddRational заполняется схожим образом с предыдущими, остаётся только изменить тип функции на INVOKE_FUNC, а при насаживании функции на интерфейс указать индекс на единицу больше предыдущей добавляемой сущности, в нашем случае это 4. Можно завести отдельную переменную под индексы и увеличивать её после добавлении нового элемента. Получим интерфейс ITypeInfo от IRational, чтобы можно было унаследовать от него наш будущий класс Rational: После настройки содержимого IRational необходимо запечатать всё его содержимое и уничтожить:  Добавление классов идёт намного быстрее, чем интерфейсов с функциями, потому что здесь требуется только добавить к классу интерфейсов‐пап: Пора сохранять результаты на диск: Данный пример показывает, что библиотеки типов можно создавать программно без знания языка определения интерфейсов IDL и компилятора midl.exe, несмотря на всю громоздкость кода. С помощью ICreateTypeLib2 и ICreateTypeInfo2 можно создавать не только описание COM‐интерфейсов и классов, но и обычных функций из DLL. Такой подход используется в Visual Basic 6 для связывания с DLL через таблицу импорта. Для упрощения кода удалены проверки на ошибки. В серьёзных программах всегда следует проверять HRESULT возвращаемого значения и предпринимать меры когда что‐то пошло не так. BASIC программист