[Из песочницы] Программное создание библиотеки типов
Библиотека типов TLB может хранить в себе информацию о возможностях COM‐компонентов: классы, интерфейсы, методы, типы параметров и возвращаемые значения. В практических руководствах по программированию COM‐компонентов обычно рассказывают как создавать библиотеку типов вручную через комплилятор midl.exe
, но сегодня рассмотрим как это делать программно через интерфейсы ICreateTypeLib2
и ICreateTypeInfo2
.
В качестве «языка программирования» будет выступать FreeBASIC.
Интерфейсы
Во фрибейсике отсутствует ключевое слово Interface
для объявления интерфейсов, но мы справимся с этой задачей и сами голыми руками. Для демонстрации серьёзности намерений построим интерфейс натуральной дроби и посмотрим на все этапы построения интерфейсов с нуля.
Упростим понятие интерфейса до набора функций, которые следует обязательно реализовать.
Интерфейс как тип данных
Определим класс натуральной дроби. У такого класса должны быть свойства, задающие числитель и знаменатель, а также мы добавим туда операцию сложения дробей. Так как нам впоследствии необходимо будет иметь доступ из унаследованного объекта к реализованным функциям, это значит, что такой набор должен состоять из указателей на наши будущие функции. Завернём их в структуру:
Type IRational
Dim GetNumerator As Function()As Integer
Dim SetNumerator As Sub(ByVal Numerator As Integer)
Dim GetDenominator As Function()As Integer
Dim SetDenominator As Sub(ByVal Denominator As Integer)
Dim AddRational As Sub(ByVal pRational As IRational Ptr)
End Type
Добавление контекста вызова
Чтобы функции интерфейса знали, какой объект их вызывает, добавим в них первым параметром указатель на объект, реализующий наш интерфейс:
Type IRational
Dim GetNumerator As Function(ByVal this As IRational Ptr)As Integer
Dim SetNumerator As Sub(ByVal this As IRational Ptr, ByVal Numerator As Integer)
Dim GetDenominator As Function(ByVal this As IRational Ptr)As Integer
Dim SetDenominator As Sub(ByVal this As IRational Ptr, ByVal Denominator As Integer)
Dim AddRational As Sub(ByVal this As IRational Ptr, ByVal pRational As IRational Ptr)
End Type
Таблица виртуальных методов
На практике набор функций интерфейса выделяют в отдельную структуру, которую теперь называеют таблицей виртуальных методов, а в самом интерфейсе оставляют ссылку на неё. Название VirtualTable часто сокращают до Vtable или даже Vtbl:
Type IRationalVirtualTable
Dim GetNumerator As Function(ByVal this As IRational Ptr)As Integer
Dim SetNumerator As Sub(ByVal this As IRational Ptr, ByVal Numerator As Integer)
Dim GetDenominator As Function(ByVal this As IRational Ptr)As Integer
Dim SetDenominator As Sub(ByVal this As IRational Ptr, ByVal Denominator As Integer)
Dim AddRational As Sub(ByVal this As IRational Ptr, ByVal pRational As IRational Ptr)
End Type
Type IRational
Dim lpVtbl As IRationalVirtualTable Ptr
End Type
Разрешение циклических ссылок
Пытаемся всё скомпилировать, но компилятор почему‐то сопротивляется такому коду. Дело в том, что виртуальная таблица IRationalVirtualTable
ссылается на интерфейс IRational
, объявленный позднее, а интерфейс IRational
ссылается на виртуальную таблицу IRationalVirtualTable
, и как их не меняй местами, ссылаться друг на друга от этого они не перестают. Выйти из ситуации поможет дополнительное имя для нашего интерфейса, введённое оператором Type
, а к названию оригинального интерфейса добавим подчёркивание:
Type IRational As IRational_
Type IRationalVirtualTable
Dim GetNumerator As Function(ByVal this As IRational Ptr)As Integer
Dim SetNumerator As Sub(ByVal this As IRational Ptr, ByVal Numerator As Integer)
Dim GetDenominator As Function(ByVal this As IRational Ptr)As Integer
Dim SetDenominator As Sub(ByVal this As IRational Ptr, ByVal Denominator As Integer)
Dim AddRational As Sub(ByVal this As IRational Ptr, ByVal pRational As IRational Ptr)
End Type
Type IRational_
Dim lpVtbl As IRationalVirtualTable Ptr
End Type
Ну вот теперь‐то определение нашего интерфейса готово.
COM‐интерфейсы
Вся технология COM построена на интерфейсах. Все COM‐интерфейсы строятся по описанному выше принципу, но с дополнительными ограничениями:
- все COM‐интерфейсы прямо или косвенно наследуются от интерфейса
IUnknown
; - за исключением методов
AddRef
иRelease
, все процедуры и функции должны возвращать тип данныхHRESULT
; - «настоящее» возвращаемое значение заносится функцией в переданный ей указатель последним параметром.
Для того, чтобы нашим интерфейсом можно было манипулировать не только через таблицу виртуальных функций, но и по именам функций из скриптовых языков программирования, рекомендуется наследовать интерфейсы не прямо от IUnknown
, а от IDispatch
. Интерфейсы, поддерживающие вызовы функций одновременно через таблицу и IDispatch
, называются дуальными. Также сменим тип операндов функций с Integer
на Long
, чтобы соответствовать типам автоматизации.
GUID
У каждого интерфейса и реализующего класса должны быть уникальные идентификаторы. Для этого используются 128‐битные числа, вычисляемые по специальному алгоритму, гарантирующему уникальность. Такие числа называют GUID
. В заголовочном файле GUID
определён в виды одноимённой структуры с дополнительными именами IID
(идентификатор интерфейса) и CLSID
(идентификатор класса). Получить GUID
можно через утилиту guidgen.exe
либо через функцию CoCreateGuid
. В программе GUID для интерфейсов и классов записываются так:
' Идентификатор интерфейса IRational
' {4116B36A-0B0D-48FD-8DB6-B9867F2A1A37}
Dim Shared IID_IRational As IID = Type(&h4116b36a, &hb0d, &h48fd, _
{&h8d, &hb6, &hb9, &h86, &h7f, &h2a, &h1a, &h37})
' Идентификатор класса Rational, реализующего IRational
' {DD6C5B70-592D-41C1-A391-BCB8C7F7639A}
Dim Shared CLSID_Rational As CLSID = Type(&hdd6c5b70, &h592d, &h41c1, _
{&ha3, &h91, &hbc, &hb8, &hc7, &hf7, &h63, &h9a})
Переделывание интерфейса IRational в COM‐совместимый
Изменим наш интерфейс в соответствии с этими требованиями, добавив заголовочные файлы и сторожей от повторного включения кода. Вот окончательный заголовочный файл IRational.bi
:
#ifndef IRATIONAL_BI
#define IRATIONAL_BI
#ifndef unicode
#define unicode
#endif
#include once "windows.bi"
#include once "win\ole2.bi"
' {4116B36A-0B0D-48FD-8DB6-B9867F2A1A37}
Dim Shared IID_IRational As IID = Type(&h4116b36a, &hb0d, &h48fd, _
{&h8d, &hb6, &hb9, &h86, &h7f, &h2a, &h1a, &h37})
' {DD6C5B70-592D-41C1-A391-BCB8C7F7639A}
Const CLSIDS_Rational = "{DD6C5B70-592D-41C1-A391-BCB8C7F7639A}"
Dim Shared CLSID_Rational As CLSID = Type(&hdd6c5b70, &h592d, &h41c1, _
{&ha3, &h91, &hbc, &hb8, &hc7, &hf7, &h63, &h9a})
Type IRational As IRational_
Type IRationalVirtualTable
' Наследование от IDispatch
Dim VirtualTable As IDispatchVtbl
Dim GetNumerator As Function(ByVal this As IRational Ptr, ByVal pResult As Long Ptr)As HRESULT
Dim SetNumerator As Function(ByVal this As IRational Ptr, ByVal Numerator As Long)As HRESULT
Dim GetDenominator As Function(ByVal this As IRational Ptr, ByVal pResult As Long Ptr)As HRESULT
Dim SetDenominator As Function(ByVal this As IRational Ptr, ByVal Denominator As Long)As HRESULT
Dim AddRational As Function(ByVal this As IRational Ptr, ByVal pRational As IRational Ptr)As HRESULT
End Type
Type IRational_
Dim lpVtbl As IRationalVirtualTable Ptr
End Type
#endif
Библиотека типов
Наша библиотека типов будет состоять из определения интерфейса IRational
и реализующего его класса Rational
.
Подготовительный этап
Для работы среды COM её следует инициализировать вызовом CoInitialize(0)
. Когда она больше не нужна, вызываем соответствующий CoUnInitialize()
.
Определим идентификатор будущей библиотеки:
' {23F94DA0-5C11-46C1-9F27-6A3FE27985CF}
Dim Shared LIBID_Rational As GUID = Type(&h23f94da0, &h5c11, &h46c1, _
{&h9f, &h27, &h6a, &h3f, &he2, &h79, &h85, &hcf})
Так как IRational
наследуется от IDispatch
, то нам необходимо загрузить библиотеку stdole32.tlb
, где хранится ITypeInfo
от IDispatch
, чтобы потом добавить на него ссылку:
CoInitialize(0)
Dim pIDispatchTypeInfo As ITypeInfo Ptr = Any
Dim pStdOleTypeLib As ITypeLib Ptr = Any
LoadTypeLib("stdole32.tlb", @pStdOleTypeLib)
pStdOleTypeLib->lpVtbl->GetTypeInfoOfGuid(pStdOleTypeLib, @IID_IDispatch, @pIDispatchTypeInfo)
' stdole32.tlb больше не нужна
pStdOleTypeLib->lpVtbl->Release(pStdOleTypeLib)
Создание библиотеки
Получить интерфейс ICreateTypeLib2
для создания библиотеки можно функцией CreateTypeLib2
. Она принимает три параметра: тип системы (SYS_MAC
, SYS_WIN16
, SYS_WIN32
или SYS_WIN64
), имя файла библиотеки и указатель на интерфейс.
Dim pCreateTypeLib As ICreateTypeLib2 Ptr = Any
CreateTypeLib2(SYS_WIN32, "Rational.tlb", @pCreateTypeLib)
' Заполняем имя библиотеки, GUID, версию, язык и описание
pCreateTypeLib->lpVtbl->SetName(pCreateTypeLib, "Rational")
pCreateTypeLib->lpVtbl->SetGuid(pCreateTypeLib, @LIBID_Rational)
pCreateTypeLib->lpVtbl->SetVersion(pCreateTypeLib, 1, 0)
pCreateTypeLib->lpVtbl->SetLcid(pCreateTypeLib, 1049) ' русский язык
pCreateTypeLib->lpVtbl->SetDocString(pCreateTypeLib, "Библиотека натуральных дробей")
Добавление интерфейса IRational в библиотеку
Метод CreateTypeInfo
интерфейса ICreateTypeLib
позволяет добавлять в библиотеку интерфейсы, классы, модули с функциями, перечисления, структуры, объединения и псевдонимы. Для этого ему нужно передать одно из значений перечисления TYPEKIND
.
Тип сущности, TYPEKIND | Описание |
---|---|
TKIND_ALIAS | Тип, который является псевдонимом для другого типа. |
TKIND_INTERFACE | Интерфейс с чистыми виртуальными функциями, то есть такими, у которых нет реализации. |
TKIND_COCLASS | Класс, унаследованный от интерфейсов. |
TKIND_DISPATCH | Набор методов и свойств, доступных через IDispatch.Invoke . По умолчанию дуальные интерфейсы возвращают TKIND_DISPATCH . |
TKIND_ENUM | Перечисление. |
TKIND_MAX | Метка окончания перечисления. |
TKIND_MODULE | Модуль, который может содержать только статические функции и данные (например, DLL). |
TKIND_RECORD | Структура без методов. |
TKIND_UNION | Объединение переменных, имеющих нулевое смещение. |
Мы будем рассматривать добавление только классов и интерфейсов. Возвращаемым значением является ICreateTypeInfo
для настройки полученной сущности.
Dim pCreateTypeInfoIRational As ICreateTypeInfo Ptr = Any
pCreateTypeLib->lpVtbl->CreateTypeInfo(pCreateTypeLib, @IRationalInterfaceName, TKIND_INTERFACE, @pCreateTypeInfoIRational)
' Устанавливаем IID и текстовое описание
pCreateTypeInfoIRational->lpVtbl->SetGuid(pCreateTypeInfoIRational, @IID_IRational)
pCreateTypeInfoIRational->lpVtbl->SetDocString(pCreateTypeInfoIRational, @"Интерфейс для поддержки натуральных дробей")
' Интерфейс будет дуальным и поддерживающим автоматизацию
pCreateTypeInfoIRational->lpVtbl->SetTypeFlags(pCreateTypeInfoIRational, TYPEFLAG_FDUAL Or TYPEFLAG_FOLEAUTOMATION)
Для наследования необходимо создать ссылку на интерфейс‐папу. Небольшая палочка в колесо: придётся самостоятельно следить за индексами добавляемых ссылок на всех отцов.
' Наследуем от IDispatch
Dim RefType As HREFTYPE = Any
hr = pCreateTypeInfoIRational->lpVtbl->AddRefTypeInfo(pCreateTypeInfoIRational, pIDispatchTypeInfo, @RefType)
' 0 — это индекс ссылки
hr = pCreateTypeInfoIRational->lpVtbl->AddImplType(pCreateTypeInfoIRational, 0, RefType)
' IDispatchTypeInfo больше не потребуется
pIDispatchTypeInfo->lpVtbl->Release(pIDispatchTypeInfo)
Добавление функций в интерфейс IRational
У функций есть параметры и возвращаемое значение. Функции представлены структурой FUNCDESC
, параметры — массивом из структур ELEMDESC
. Так как все функции интерфейса возвращают HRESULT
, можно заранее создать возвращаемое значение для всех функций:
Dim HresultReturnedValue As ELEMDESC
With HresultReturnedValue
.tdesc.vt = VT_HRESULT
.idldesc.wIDLFlags = IDLFLAG_NONE
End With
Здесь vt
определяет одно из значений перечисления VARENUM
, а IDLFLAG_NONE
указывает, что флагов не присвоено. Флаги могут принимать комбинацию из следующих значений:
Флаги параметров и возвращаемых значений | Описание |
---|---|
IDLFLAG_FIN | Входящий параметр. |
IDLFLAG_FOUT | Исходящий параметр, возвращает сведения из вызываемого объекта в вызывающий объект, обычно является указателем. |
IDLFLAG_FRETVAL | «Настоящее» возвращаемое значение функции, обычно комбинируют с флагом IDLFLAG_FOUT . |
IDLFLAG_NONE | Не установлено. |
В некоторых случаях у объекта могут существовать свойства. Со стороны свойство выглядит как переменная, но изнутри обеспечивается с помощью функций установки и возврата значения. Функции можно описать одним из следующих значений:
Виды функций по поведению, INVOKEKIND | Описание |
---|---|
INVOKE_FUNC | Обычная функция. |
INVOKE_PROPERTYGET | Свойство, возвращающее значение. |
INVOKE_PROPERTYPUT | Свойство, устанавливающее значение. |
INVOKE_PROPERTYPUTREF | Свойство, устанавливающее значение по ссылке. |
Ещё функции подразделаются по реализованности:
Виды функций по реализованности, FUNCKIND | Описание |
---|---|
FUNC_STATIC | Статическая функция с реализацией, без контекста вызова. Такие функции обычно живут в DLL. |
FUNC_NONVIRTUAL | Статическая функция‐член класса с реализацией, внутри себя принимает неявный контекст вызова. |
FUNC_VIRTUAL | Виртуальная функция‐член класса с реализацией, внутри себя принимает неявный контекст вызова. |
FUNC_PUREVIRTUAL | Чистая виртуальная функция, внутри себя принимает неявный контекст вызова. |
FUNC_DISPATCH | Функция доступна только через IDispatch.Invoke . |
Функция GetNumerator
В высокоуровневых языка программирования контексты вызова функций (указатель на объект), как и таблицы виртуальных функций, скрыты от программиста и не учитываются в интерфейсах, поэтому при настройке параметров мы не будем их указывать. Введём описание параметров функции GetNumerator
:
Const MaxArgumentGetNumeratorNamesLength As UINT = 2
Const MaxArgumentGetNumeratorLength As SHORT = 1
' Массив из имени функции и параметра
' Так как эта функция часть свойства, то мы указываем её имя без префикса Get.
Dim GetNumeratorArgumentNames(MaxArgumentGetNumeratorNamesLength - 1) As WString Ptr = Any
GetNumeratorArgumentNames(0) = @"Numerator"
GetNumeratorArgumentNames(1) = @"pResult"
' Исходящий параметр — «настоящее» возвращаемое значение
Dim GetNumeratorArguments(MaxArgumentGetNumeratorLength - 1) As ELEMDESC
Dim RetvalGetNumerator As TYPEDESC
With RetvalGetNumerator
.vt = VT_I4 ' Тип автоматизации Long
End With
With GetNumeratorArguments(0)
.tdesc.vt = VT_PTR ' Указатель
.tdesc.lptdesc = @RetvalGetNumerator ' Тип данных указателя
.idldesc.wIDLFlags = IDLFLAG_FOUT Or IDLFLAG_FRETVAL
End With
Заполнение структуры FUNCDESC
для функции GetNumerator
:
Dim GetNumeratorDefinition As FUNCDESC = Any
With GetNumeratorDefinition
.memid = 0 ' Номер функции при вызове через IDispatch.Invoke, у парных функций‐свойств это поле должно совпадать
.lprgscode = 0 ' Массив ориентировочных возвращаемых значений HRESULT
.cScodes = 0 ' Размер массива возвращаемых значений
.lprgelemdescParam = @GetNumeratorArguments(0) ' Указатель на массив параметров функций
.cParams = MaxArgumentGetNumeratorLength ' Количество параметров
.cParamsOpt = 0 ' Количество необзязательных параметров
.elemdescFunc = HresultReturnedValue ' Функция возвращает HRESULT
.funckind = FUNC_PUREVIRTUAL ' Чисто виртуальная функция
.invkind = INVOKE_PROPERTYGET ' Это получение свойства
.callconv = CC_STDCALL ' Соглашение о вызове STDCALL
.oVft = 0 ' Индекс функции в виртуальной таблице, указывается только для FUNC_VIRTUAL
.wFuncFlags = 0
End With
Теперь функцию можно насаживать на интерфейс. Здесь опять придётся следить за индексом добавляемой функции, в нашем случае это 0:
' Функция и параметры
pCreateTypeInfoIRational->lpVtbl->AddFuncDesc(pCreateTypeInfoIRational, 0, @GetNumeratorDefinition)
' Имена функций и параметров
pCreateTypeInfoIRational->lpVtbl->SetFuncAndParamNames(pCreateTypeInfoIRational, 0, @GetNumeratorArgumentNames(0), MaxArgumentGetNumeratorNamesLength)
' Текстовое описание функции
pCreateTypeInfoIRational->lpVtbl->SetFuncDocString(pCreateTypeInfoIRational, 0, @"Возвращает числитель")
Функция SetNumerator
Похожим образом определяем вторую часть свойства Numerator
:
Const MaxArgumentSetNumeratorNamesLength As UINT = 2
Const MaxArgumentSetNumeratorLength As SHORT = 1
Dim SetNumeratorArgumentNames(MaxArgumentSetNumeratorNamesLength - 1) As WString Ptr = Any
SetNumeratorArgumentNames(0) = @"Numerator"
SetNumeratorArgumentNames(1) = @"Numerator"
' Всего один входящий параметр
Dim SetNumeratorArguments(MaxArgumentSetNumeratorLength - 1) As ELEMDESC
With SetNumeratorArguments(0)
.tdesc.vt = VT_I4 ' Long
.idldesc.wIDLFlags = IDLFLAG_FIN
End With
Dim SetNumeratorDefinition As FUNCDESC = Any
With SetNumeratorDefinition
.memid = 0 ' Номер функции такой же как у GetNumerator
.lprgscode = 0
.cScodes = 0
.lprgelemdescParam = @SetNumeratorArguments(0) ' Массив параметров функций
.cParams = MaxArgumentSetNumeratorLength ' Количество параметров
.cParamsOpt = 0
.elemdescFunc = HresultReturnedValue
.funckind = FUNC_PUREVIRTUAL
.invkind = INVOKE_PROPERTYPUT ' Установка свойства
.callconv = CC_STDCALL
.oVft = 0
.wFuncFlags = 0
End With
Для добавления функции в интерфейс опять придётся следить за индексами вручную. Однако в методе SetFuncAndParamNames
необходимо указать индекс предыдущей функции GetNumerator
, так как она является парной для свойства.
pCreateTypeInfoIRational->lpVtbl->AddFuncDesc(pCreateTypeInfoIRational, 1, @SetNumeratorDefinition)
' Вот здесь указываем 0 — индекс предыдущей части свойства
pCreateTypeInfoIRational->lpVtbl->SetFuncAndParamNames(pCreateTypeInfoIRational, 0, @SetNumeratorArgumentNames(0), MaxArgumentSetNumeratorNamesLength)
pCreateTypeInfoIRational->lpVtbl->SetFuncDocString(pCreateTypeInfoIRational, 1, @"Устанавливает числитель")
Аналогичным образом добавляются функции для возвращения и установки знаменателя, с индексами 2 и 3.
Функция AddRational
Функция AddRational
принимает параметр типа указатель на IRational
. Но в автоматизации такой тип данных отсутствует, поэтому заменяем его на IDispatch
:
Const MaxArgumentAddRationalLength As SHORT = 1
Dim AddRationalArguments(MaxArgumentAddRationalLength - 1) As ELEMDESC
Dim RetvalAddRational As TYPEDESC
With RetvalAddRational
.vt = VT_DISPATCH
End With
With AddRationalArguments(0)
.tdesc.lptdesc = @RetvalAddRational
.tdesc.vt = VT_PTR ' IDispatch Ptr
.idldesc.wIDLFlags = IDLFLAG_FIN
End With
В обозревателе объектов параметр будет виден как Object
, а передача его будет идти по ссылке ByRef
.
Определение функции AddRational
заполняется схожим образом с предыдущими, остаётся только изменить тип функции на INVOKE_FUNC
, а при насаживании функции на интерфейс указать индекс на единицу больше предыдущей добавляемой сущности, в нашем случае это 4. Можно завести отдельный переменную под индексы и увеличивать её после добавлении нового элемента.
Закрытие интерфейса IRational
Получим интерфейс ITypeInfo
от IRational
, чтобы можно было унаследовать от него наш будущий класс Rational
:
Dim pIRationalTypeInfo As ITypeInfo Ptr = Any
pCreateTypeInfoIRational->lpVtbl->QueryInterface(pCreateTypeInfoIRational, @IID_ITypeInfo, @pIRationalTypeInfo)
После настройки содержимого IRational
необходимо запечатать всё его содержимое и уничтожить:
pCreateTypeInfoIRational->lpVtbl->LayOut(pCreateTypeInfoIRational)
pCreateTypeInfoIRational->lpVtbl->Release(pCreateTypeInfoIRational)
Добавление класса Rational в библиотеку
Добавление классов идёт намного быстрее, чем интерфейсов с функциями, потому что здесь требуется только добавить к классу интерфейсов‐пап:
' Добавляем класс
Dim pCreateTypeInfoRational As ICreateTypeInfo Ptr = Any
pCreateTypeLib->lpVtbl->CreateTypeInfo(pCreateTypeLib, @"Rational", TKIND_COCLASS, @pCreateTypeInfoRational)
' Устанавливаем GUID и текстовое описание
pCreateTypeInfoRational->lpVtbl->SetGuid(pCreateTypeInfoRational, @CLSID_Rational)
pCreateTypeInfoRational->lpVtbl->SetDocString(pCreateTypeInfoRational, @"Натуральная дробь")
' Наследуемся от IRational
Dim RefType As HREFTYPE = Any
pCreateTypeInfoRational->lpVtbl->AddRefTypeInfo(pCreateTypeInfoRational, pIRationalTypeInfo, @RefType)
pCreateTypeInfoRational->lpVtbl->AddImplType(pCreateTypeInfoRational, 0, RefType)
pIRationalTypeInfo->lpVtbl->Release(pIRationalTypeInfo)
' Запечатываем класс и уничтожаем его
pCreateTypeInfoRational->lpVtbl->LayOut(pCreateTypeInfoRational)
pCreateTypeInfoRational->lpVtbl->Release(pCreateTypeInfoRational)
Сохранение библиотеки
Пора сохранять результаты на диск:
pCreateTypeLib->lpVtbl->SaveAllChanges(pCreateTypeLib)
pCreateTypeLib->lpVtbl->Release(pCreateTypeLib)
CoUnInitialize()
Выводы
Данный пример показывает, что библиотеки типов можно создавать программно без знания языка определения интерфейсов IDL и компилятора midl.exe
, несмотря на всю громоздкость кода.
С помощью ICreateTypeLib2
и ICreateTypeInfo2
можно создавать не только описание COM‐интерфейсов и классов, но и обычных функций из DLL. Такой подход используется в Visual Basic 6 для связывания с DLL через таблицу импорта.
Для упрощения кода удалены проверки на ошибки. В серьёзных программах всегда следует проверять HRESULT
возвращаемого значения и предпринимать меры когда что‐то пошло не так.