...

воскресенье, 29 апреля 2018 г.

[Из песочницы] Программное создание библиотеки типов

Библиотека типов 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 возвращаемого значения и предпринимать меры когда что‐то пошло не так.

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий