- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть II
- •Интерфейсы не изменяются
- •Полиморфизм
- •Что за интерфейсом
- •Таблица виртуальных функций
- •Указатели vtbl и данные экземпляра
- •Множественные экземпляры
- •Разные классы, одинаковые vtbl
- •Запрос интерфейса
- •IUnknown
- •Получение указателя на IUnknown
- •Знакомство с QueryInterface
- •Использование QueryInterface
- •Реализация QueryInterface
- •А теперь все вместе
- •Правила и соглашения QueryInterface
- •Вы всегда получаете один и тот же IUnknown
- •Вы можете получить интерфейс снова, если смогли получить его раньше
- •Вы можете снова получить интерфейс, который у Вас уже есть
- •Вы всегда можете вернуться туда, откуда начали
- •Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно
- •QueryInterface определяет компонент
- •Вы не можете воспользоваться всеми знаниями сразу
- •Работа с новыми версиями компонентов
- •Когда нужно создавать новую версию
- •Имена версий интерфейсов
- •Неявные соглашения
- •Управление временем жизни
- •Подсчет ссылок
- •Подсчет ссылок на отдельные интерфейсы
- •Реализация AddRef и Release
- •Когда подсчитывать ссылки
- •Оптимизация подсчета ссылок
- •Правила подсчета ссылок
- •Амуниция пожарного, резюме
- •Создание компонента
- •Экспорт функции из DLL
- •Загрузка DLL
- •Разбиваем монолит
- •Тексты программ
- •Связки объектов
- •Негибкое связывание, резюме
- •HRESULT
- •Поиск HRESULT
- •Использование HRESULT
- •Определение собственных кодов ошибки
- •GUID
- •Зачем нужен GUID?
- •Объявление и определение GUID
- •Сравнение GUID
- •Передача GUID по ссылке
- •Реестр Windows
- •Организация Реестра
- •Редактор Реестра
- •Необходимый минимум
- •Другие детали Реестра
- •ProgID
- •Саморегистрация
- •Категории компонентов
- •OleView
- •Некоторые функции библиотеки COM
- •Инициализация библиотеки COM
- •Управление памятью
- •Преобразование строк в GUID
- •Резюме
- •CoCreateInstance
- •Прототип CoCreateInstance
- •Использование CoCreateInstance
- •Контекст класса
- •Листинг кода клиента
- •Но CoCreateInstance недостаточно гибка
- •Фабрики класса
- •Использование CoGetClassObject
- •IClassFactory
- •CoCreateInstance vs. CoGetClassObject
- •Фабрики класса инкапсулируют создание компонентов
- •Реализация фабрики класса
- •Использование DllGetClassObject
- •Общая картина
- •Листинг кода компонента
- •Последовательность выполнения
- •Регистрация компонента
- •Несколько компонентов в одной DLL
- •Повторное применение реализации фабрики класса
- •Выгрузка DLL
- •Использование DllCanUnloadNow
- •LockServer
- •Резюме
- •Включение и агрегирование
- •Включение
- •Агрегирование
- •Сравнение включения и агрегирования
- •Реализация включения
- •Расширение интерфейсов
- •Реализация агрегирования
- •Магия QueryInterface
- •Неверный IUnknown
- •Интерфейсы IUnknown для агрегирования
- •Создание внутреннего компонента
- •Законченный пример
- •Слепое агрегирование
- •Агрегирование и включение в реальном мире
- •Предоставление информации о внутреннем состоянии
- •Моделирование виртуальных функций
- •Резюме
- •Упрощения на клиентской стороне
- •Smart-указатели на интерфейсы
- •Классы-оболочки C++
- •Упрощения на серверной стороне
- •Базовый класс CUnknown
- •Базовый класс CFactory
- •Использование CUnknown и CFactory
- •Резюме
- •Разные процессы
- •Локальный вызов процедуры
- •Маршалинг
- •DLL заместителя/заглушки
- •Введение в IDL/MIDL
- •Примеры описаний интерфейсов на IDL
- •Компилятор MIDL
- •Реализация локального сервера
- •Работа примера программы
- •Нет точек входа
- •Запуск фабрик класса
- •Изменения в LockServer
- •Удаленный сервер
- •Что делает DCOMCNFG.EXE?
- •Но как это работает?
- •Другая информация DCOM
- •Резюме
- •Новый способ общения
- •Старый способ общения
- •Использование IDispatch
- •Параметры Invoke
- •Примеры
- •Тип VARIANT
- •Тип данных BSTR
- •Тип данных SAFEARRAY
- •Библиотеки типа
- •Создание библиотеки типа
- •Библиотеки типа в Реестре
- •Реализация IDispatch
- •Генерация исключений
- •Маршалинг
- •Что Вы хотите сделать сегодня?
- •Потоковые модели COM
- •Потоки Win32
- •Подразделение
- •Разделенные потоки
- •Свободные потоки
- •Маршалинг и синхронизация
- •Реализация модели разделенных потоков
- •Автоматический маршалинг
- •Ручной маршалинг
- •Настало время написать программу
- •Пример с разделенным потоком
- •Реализация модели свободных потоков
- •Пример со свободным потоком
- •Оптимизация маршалинга для свободных потоков
- •Информация о потоковой модели в Реестре
- •Резюме
- •Программа Tangram
- •Tangram в работе
- •Детали и составные части
- •Клиентский EXE-модуль
- •Компонент TangramModel
- •Компоненты TangramGdiVisual и TangramGLVisual
- •Компоненты TangramGdiWorld и TangramGLWorld
- •Что демонстрирует пример
- •Файлы IDL
- •Файл DLLDATA.C
- •Циклический подсчет ссылок
- •Не вызывайте AddRef
- •Используйте явное удаление
- •Используйте отдельный компонент
- •События и точки подключения
- •IEnumXXX
191
FADF_BSTR |
Массив BSTR |
FAFD_UNKNOWN |
Массив IUnknown* |
FADF_DISPATCH |
Массив IDispatch* |
FADF_VARIANT |
Массив VARIANT |
Это поле также описывает, как массив был выделен:
FADF_AUTO |
Массив размещен в стеке |
FADF_STATIC |
Массив размещен статически |
FADF_EMBEDDED |
Массив входит в структуру |
FADF_FIXEDSIZE |
Размер и местоположение массива нельзя менять |
Библиотека Автоматизации — OLEAUT32.DLL — включает целый ряд функций для манипулирования SAFEARRAY. Названия всех таких функций начинаются с префикса SafeArray. Поищите их сами в диалоговой справочной системе.
Мы знаем, как заполнять переменные VARIANT, которые используются для построения структуры DISPPARAMS, которая передается IDispatch::Invoke, с помощью которой мы можем вызвать диспетчерские методы и получать доступ к диспетчерским свойствам. Теперь пришла пора кратко рассмотреть библиотеки типа
— независимый от языка эквивалент заголовочных файлов C++.
Библиотеки типа
Как мы уже видели, программа на Visual Basic или С++ может управлять компонентом через disp-интерфейс, ничего не зная о типах, связанных с этим интерфейсом или его методами. Однако, если Вы можете засунуть горошину в ухо, это не означает, что так и следует поступать. Точно так же, если Вы можете писать программу на Visual Basic без информации о типах, это не означает, что так и надо делать.
Описанные в предыдущем разделе проверка и преобразование типов VARIANT на этапе выполнения требуют много времени и могут привести к скрытым ошибкам в программе. Программист может случайно перепутать местами два параметра в вызове функции, и компонент успешно преобразует их типы. Большое преимущество С++ перед С — более строгая проверка типов; она до некоторой степени обеспечивает уверенность в том, что программа работает, как предполагалось.
Нам нужен независимый от языка эквивалент заголовочных файлов С++, который подходил бы для интерпретируемых языков и сред макропрограммирования. Решение есть — библиотека типа (type library) СОМ, которая предоставляет информацию типа о компонентах, интерфейсах, методах, свойствах, аргументах и структурах. Содержимое библиотеки типа аналогично содержимому заголовочного файла С++. Библиотека типа
— это откомпилированная версия файла IDL, к которой возможен доступ из программы. Это не текст на каком-то языке, требующий синтаксического разбора, а двоичный файл. Библиотека Автоматизации предоставляет стандартные компоненты для создания и чтения таких двоичных файлов.
Без библиотеки типа возможности Visual Basic работать с компонентами ограничены disp-интерфейсами. Если же библиотека типа имеется, Visual Basic может работать с компонентом напрямую через vtbl дуального интрфейса. Доступ через vtbl быстрее, и он безопаснее с точки зрения приведения типа.
Да, пока не забыл, — библиотека типа может также содержать строки справочной информации для всех содержащихся в ней компонентов, интерфейсов и функций. С помощью средства просмотра объектов, подобного имеющемуся в Visual Basic, программист может легко получить подсказку о любом свойстве или методе. Не правда ли, замечательно?
Создание библиотеки типа
Библиотеку типа создает функция CreateTypeLib из библиотеки Автоматизации. CreateTypeLib возвращает интерфейс IcreateTypeLib, который можно использовать для занесения в библиотеку различной информации. Вряд ли Вам когда-нибудь потребуется использовать этот интерфейс; вместо него можно пользоваться IDL и компилятором MIDL. В гл. 10 мы использовали IDL и компилятор MIDL для генерации кода DLL заместителя/заглушки, но они подходят и для генерации библиотек типа.
ODL и MkTypLib
В «старые» времена компилятор MIDL нельзя было использовать для генерации библиотек типа. Вместо описания библиотек на IDL приходилось использовать другой язык — ODL. ODL компилировался в библиотеку типа с помощью программы MkTypLib. ODL был похож на IDL, но отличий было достаточно,
192
чтобы затруднить их совместное использование. Поддержка двух файлов, содержащих одну и ту же информацию, — также напрасный расход времени. К счастью, IDL и MIDL при разработке Windows NT 4.0 были расширены для поддержки создания библиотек типа. Теперь ODL и MkTypLib стали не нужны и более не используются.
Оператор library
Основа создания библиотеки типа при помощи IDL — оператор library. Все, что находится внутри блока кода, ограниченного фигурными скобками, которые следуют за ключевым словом library, будет компилироваться в библиотеку типа. Файл IDL из примера гл. 11 показан в листинге 11-1. как видите, у библиотеки типа есть свои
GUID, версия и helpstring.
SERVER.IDL
//
// Server.idl – Исходный файл IDL для Server.dll
//
//Этот файл будет обрабатываться компилятором MIDL для
//генерации библиотеки типа (Server.tlb) кода маршалинга.
//
// Интерфейс IX
[
object,
uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682), helpstring("Интерфейс IX"),
pointer_default(unique), dual,
oleautomation
]
interface IX : IDispatch
{
import "oaidl.idl";
HRESULT Fx();
HRESULT FxStringIn([in] BSTR bstrIn);
HRESULT FxStringOut([out, retval] BSTR* pbstrOut); HRESULT FxFakeError();
};
//
// Описание компонента и библиотеки типа
//
[
uuid(D3011EE1-B997-11CF-A6BB-0080C7B2D682),
version(1.0),
helpstring("Основы COM, Глава 11 1.0 Библиотека типа")
]
library ServerLib
{
importlib("stdole32.tlb");
// Компонент
[
uuid(0C092C2C-882C-11CF-A6BB-0080C7B2D682), helpstring("Класс компонента")
]
coclass Component
{
[default] interface IX;
};
};
Листинг 11-1 Файл IDL, используемый для генерации библиотеки SERVER.TLB
Оператор coclass определяет компонент; в данном случае это Component с единственным интерфейсом IX. Компилятор MIDL сгенерирует библиотеку типа, содержащую Component и IX. Component добавляется к библиотеке типа, так как оператор coclass находится внутри оператора library. Интерфейс IX включается в библиотеку потому, что на него есть ссылка внутри оператора library.
193
Когда компилятор MIDL встречает в файле IDL оператор library, он автоматически генерирует библиотеку типа. В гл. 10 Вы видели, что компилятор MIDL генерировал библиотеку типа SERVER.TLB, даже когда она не была нам нужна.
Распространение библиотек типа
После генерации библиотеки типа Вы можете либо поставлять ее в виде отдельного файла, либо включить ее в Ваш EXE или DLL как ресурс. Большинство разработчиков предпочитает второй вариант, поскольку он упрощает установку приложения.
Использование библиотек типа
Первый шаг при использовании библиотек типа — ее загрузка. Для этого имеется несколько функций. Первая, которую следует попробовать, — LoadRegTypeLib, пытающаяся загрузить библиотеку по информации из Реестра Windows. Если эта функция потерпела неудачу, Вам следует использовать LoadTypeLib, которая загружает библиотеку с диска по имени файла, либо LoadTypeLibFromResource, которая загружает библиотеку типа из ресурса в EXE или DLL. LoadTypeLib должна в процесса загрузки регистрировать для Вас библиотеку типа. Однако если ей задано имя полного пути, библиотека зарегистрирована не будет (см. PSS ID Number Q131055). Следовательно, после успешного вызова LoadTypeLib стоит вызвать RegisterTypeLib. Соответствующий код приведен в листинге 11-2.
Модифицированный код инициализации компонента из CMPNT.CPP
HRESULT CA::Init()
{
HRESULT hr;
// Динамически загрузить TypeInfo, если он еще не загружен if (m_pITypeInfo == NULL)
{
ITypeLib* pITypeLib = NULL; |
|
hr = ::LoadRegTypeLib(LIBID_ServerLib, |
// Номера версии |
1, 0, |
|
0x00, |
|
&pITypeLib); |
|
if (FAILED(hr))
{
// Загрузить и зарегистрировать библиотеку типа
hr = ::LoadTypeLib(wszTypeLibFullName, &pITypeLib); if(FAILED(hr))
{
trace("Вызов LoadTypeLib неудачен", hr); return hr;
}
// Убедиться, что библиотека типа зарегистрирована
hr = RegisterTypeLib(pITypeLib, wszTypeLibFullName, NULL); if(FAILED(hr))
{
trace("Вызов RegisterTypeLib неудачен", hr); return hr;
}
}
// Получить информацию типа для интерфейса объекта
hr = pITypeLib->GetTypeInfoOfGuid(IID_IX, &m_pITypeInfo); pITypeLib->Release();
if (FAILED(hr))
{
trace("Вызов GetTypeInfoOfGuid неудачен", hr); return hr;
}
}
return S_OK;
}
Листинг 11-2 Загрузка, регистрация и использование библиотеки типа