- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
82
Сравнение GUID
Для сравнения GUID в OBJBASE.H определен operator==:
inline BOOL operator ==(const GUID& guid1, const GUID& guid2)
{
return !memcmp(&guid1, &guid2, sizeof(GUID));
}
Нам уже приходилось использовать эту операцию в QueryInterface. Если Вы не любите упрятывать истинный код во внешние простенькие операторы, OBJBASE.H дает определения эквивалентных по смыслу функций
IsEqualGUID, IsEqualIID и IsEqualCLSID.
Использование GUID в качестве идентификаторов компонентов
Помимо уникальной идентификации интерфейсов, GUID используется и для уникальной идентификации компонентов. В гл. 5 мы определили для создания компонентов функцию CallCreateInstance. Параметром этой функции служит строка с именем DLL, в которой содержится компонент:
IUnknown* CallCreateInstance(char* name);
В следующей главе мы заменим эту функцию на функцию библиотеки СОМ CoCreateInstance. Последняя использует для идентификации компонента не строку, а GUID. Такой GUID в СОМ называется идентификатором класса. Чтобы отличать идентификаторы классов от IID, для них используют тип CLSID.
Передача GUID по ссылке
Поскольку размер GUID 16 байтов, мы будем передавать их не по значению, а по ссылке. Именно поэтому параметром QueryInterface является ссылка на константу. Если для Вас утомительно все время писать
const IID&
можете использовать эквивалентное выражение REFID. Точно так же для передачи идентификаторов классов можно использовать REFCLSID, а для передачи GUID — REFGUID.
Теперь давайте рассмотрим, как компоненты регистрируются в системе (чтобы клиенты смогли их найти и использовать).
Реестр Windows
FAA ведет реестр всех летательных аппаратов, включая самодельные. По этому реестру можно определить, кто хозяин самолета. В этой главе мы рассмотрим чем-то похожий реестр, позволяющий определить, какой DLL принадлежит данный компонент.
Вгл. 5 при создании компонента мы передавали функции CallCreateInstance имя файла соответствующей DLL. В следующей главе мы собираемся заменить CallCreateInstance функцией библиотеки СОМ CoCreateInstance. Для идентификации компонента CoCreateInstance вместо имени файла использует CLSID (по нему определяется имя файла DLL). Компоненты помещают имена своих файлов, индексированные CLSID, в Реестр Windows. CoCreateInstance отыскивает имя файла, используя CLSID как ключ.
Вреальной жизни реестр — это учетная книга для записи предметов, имен или действий. В Windows реестр — это общедоступная база данных операционной системы. Реестр содержит информацию об аппаратном и программном обеспечении, о конфигурации компьютера и о пользователях. Любая программа для Windows может добавлять и считывать информацию из Реестра; клиенты могут искать там нужные компоненты. Но прежде чем поместить свою информацию в Реестр, надо узнать, как он устроен.
Организация Реестра
Реестр имеет иерархическую структуру. Каждый ее элемент называется разделом (key). Раздел может включать в себя набор подразделов, набор именованных параметров и/или один безымянный параметр — параметр по умолчанию (default value). Подразделы, но не параметры, могут содержать другие подразделы и параметры. Параметры могут быть разного типа, но чаще всего мы будем записывать в Реестр строки. Структура Реестра показана на рис. 6-3.
Редактор Реестра
Реестр содержит очень много информации. По счастью, нас интересует лишь малое подмножество. Лучше всего изучать Реестр, запустив Редактор Реестра — Windows-программу, позволяющую просматривать и редактировать
83
записи. Эта программа называется REGEDT32.EXE в Windows NT и REGEDIT.EXE в Windows 95*. Одно предостережение: редактируя Реестр, чрезвычайно легко повредить систему, так что будьте осторожны.
Корень
Именованный
параметр
Именованный
параметр
Раздел |
Параметр по |
|
умолчанию |
|
|
|
|
|
|
Именованный |
|
|
параметр |
|
|
Раздел |
Параметр по |
|
умолчанию |
|
|
|
Раздел
Именованный
параметр
Раздел |
Параметр по |
|
умолчанию |
||
|
Раздел
Рис. 6-3 Структура Реестра Windows
Необходимый минимум
СОМ использует только одну ветвь дерева данных Реестра: HKEY_CLASSES_ROOT. Ниже HKEY_CLASSES_ROOT отыщите раздел CLSID. В этом разделе перечислены CLSID всех компонентов, установленных в системе. CLSID хранится в Реестре как строка формата {xxxxxxxx-xxxx-xxxx-xxxx- xxxxxxxxxxxx}. Искать CLSID в Реестре — занятие не слишком привлекательное. Поэтому в каждом разделе CLSID параметр по умолчанию задает «дружественное» имя компонента.
Пока в разделе каждого CLSID нас интересует только один подраздел — InprocServer32. Его параметр по умолчанию — имя файла DLL. Название InprocServer32 используется потому, что DLL — это сервер в процессе (in-proc); она загружается в процесс клиента и предоставляет ему сервисы. На рис. 6-4 показан пример ветви CLSID Реестра.
Как видно из рисунка, в разделе Реестра HKEY_CLASSES_ROOT\CLSID хранится CLSID компонента Tail Rotor Simulator. Дружественное имя зарегистрировано как параметр по умолчанию для CLSID компонента. Подраздел
InprocServer32 содержит имя файла DLL — C:\Helicopter\TailRotor.dll.
Имя файла и CLSID — две наиболее важные составляющие данных Реестра. Для многих компонентов СОМ ничего больше и не потребуется. Однако в некоторых случаях нужна дополнительная информация.
Другие детали Реестра
Давайте совершим краткую экскурсию по подразделам HKEY_CLASSES_ROOT. Мы уже знакомы с CLSID, и далее мы рассмотрим, какая дополнительная информация для классов хранится в этом подразделе. В начале HKEY_CLASSES_ROOT Вы можете видеть группу расширений имен файлов, зарегистрированных разными программами. После расширений следует множество других имен. По большей части это так называемые ProgID
— что расшифровывается как программный идентификатор (program identifier). Мы поговорим о ProgID немного ниже. Некоторые из имен — не ProgID, а специальные разделы реестра, похожие на CLSID. Эти разделы связывают GUID с некоторыми другими данными, например, именами файлов. Такие разделы перечислены ниже.
!" AppID — Подразделы данного раздела связывают APPID (application identifier — идентификатор приложения) с именем удаленного сервера. Этот раздел использует DCOM и будет обсуждаться в гл. 10.
* В Windows NT 4.0 также имеется программа REGEDIT.EXE — Прим. перев.
84
!" Component Categories — Эта ветвь Реестра связывает CATID (component category ID — идентификатор категории компонентов) с соответствующей категорией. Категории компонентов рассматриваются ниже.
!" Interface — Данный раздел связывает IID с информацией, специфичной для интерфейса. Эта информация нужна в основном для доступа к интерфейсу «через границы» процессов. Мы рассмотрим этот раздел в гл. 10.
!" Licenses — Раздел Licenses хранит лицензии на право использования компонентов СОМ. В этой книге лицензии рассматриваться не будут.
!" TypeLib — Помимо других данных, библиотеки типа содержат информацию о параметрах функцийчленов интерфейсов. Этот раздел связывает LIBID с именем файла, в котором хранится библиотека типа. Библиотеки типов будут обсуждаться в гл. 11.
Мой компьютер
HKEY_CLASSES_ROOT
Первыми перечислены зарегистрированные расширения имен файлов
CLSID |
Дружественное имя компонента |
|
{00000300-0000-0000-C000-000000000046} |
StdOleLink |
|
InprocServer32 |
|
ole32.dll |
|
Местоположение компонента |
|
|
Дружественное имя компонента |
|
{166769E1-88E8-11CF-A6BB-0080C7B2D682} |
Tail Rotor Simulator |
|
InprocServer32 |
C:\Helicopter\TailRotor.dll |
Местоположение компонента
Рис. 6-4 Структура подраздела CLSID Реестра
ProgID
Теперь рассмотрим ProgID более подробно. Большая часть подразделов в ветви Реестра HKEY_CLASSES_ROOT
— это ProgID. ProgID отображает «дружественную», понятную программисту строку в CLSID. Некоторые языки программирования, такие как Visual Basic, идентифицируют компоненты по ProgID, а не по CLSID. Уникальность ProgID не гарантируется, поэтому существует принципиальная опасность конфликта имен. Однако с ProgID легче работать. (Кроме того, некоторые языки программирования не поддерживают структур, и в них пришлось бы использовать строковое представление GUID.)
Соглашение об именовании ProgID
По соглашению ProgID имеет следующий формат:
<Программа>.<Компонент>.<Версия>
Вот несколько примеров из Реестра:
Visio.Application.3
Visio.Drawing.4
RealAudio.ReadAudio ActiveX Control (32-bit).1
Office.Binder.95
MSDEV.APPLICATION
JuiceComponent.RareCat.1
Но этот формат — лишь соглашение, а не жесткое правило, и в Реестре на моей машине полно компонентов, которые ему не следуют.
Во многих случаях клиента не интересует версия компонента, к которой он подключается. Таким образом, у компонента часто имеется ProgID, не зависящий от версии. Этот ProgID связывается с самой последней версией компонента из установленных в системе. Соглашение об именовании не зависящих от версии ProgID сводится к отбрасыванию номера версии. Пример такого ProgID, следующего соглашению, — MSDEV.APPLICATION.
85
ProgID в Реестре
ProgID и не зависящий от версии ProgID компонента приводятся в разделе CLSID. Однако основное назначение ProgID — обеспечить получение соответсвующего CLSID. Просматривать все разделы CLSID для поиска ProgID было бы неэффективно. В связи с этим ProgID указывается непосредственно и в разделе HKEY_CLASSES_ROOT. ProgID не предназначены для представления конечным пользователям, поэтому по умолчанию значение любого раздела ProgID — дружественное для пользователя имя. В разделе ProgID имеется подраздел с именем CLSID, который содержит CLSID компонента в качестве значения по умолчанию. Не зависящий от версии ProgID также приводится непосредственно в разделе HKEY_CLASSES_ROOT. У него есть дополнительный подраздел CurVer, содержащий ProgID текущей версии компонента.
На рис. 6-5 представлен расширенный пример с рис. 6-4, включающий ProgID. В раздел CLSID компонента добавлен раздел с именем ProgID, и в него помещено значение Helicopter.TailRotor.1 — ProgID компонента. Не зависящий от версии ProgID сохранен в разделе VersionIndependentProgID. В данном примере не зависящий от версии ProgID — Helicopter.TailRotor.
HKEY_CLASSES_ROOT
CLSID
{166769E1-88E8-11CF-A6BB-0080C7B2D682} Модель хвостового винта
InprocServer32 C:\Helicopter\TailRotor.dll
ProgID Helicopter.TailRotor.1
Не зависящий от |
VesionIndependentProgID |
Helicopter.TailRotor |
|
версии ProgID |
|||
Helicopter.TailRotor |
|
Модель хвостового винта |
|
|
CLSID |
{166769E1-88E8-11CF-A6BB-0080C7B2D682} |
|
|
CurVer |
|
Helicopter.TailRotor.1 |
Helicopter.TailRotor.1 |
|
Модель хвостового винта |
|
ProgID |
CLSID |
{166769E1-88E8-11CF-A6BB-0080C7B2D682} |
Рис. 6-5 Организация разделов Реестра, в которых содержится информация, имеющая отношение к ProgID
На рисунке также показаны отдельные разделы Helicopter.TailRotor и Helicopter.TailRotor.1, расположенные непосредственно в HKEY_CLASSES_ROOT. В разделе Helicopter.TailRotor.1 имеется единственный подраздел — CLSID, который содержит CLSID компонента. Не зависящий от версии ProgID Helicopter.TailRotor содержит подразделы CLSID и CurVer. Значение по умолчанию подраздела CurVer — ProgID текущей версии компонента,
Helicopter.TailRotor.1.
От ProgID к CLSID
После того, как Вы поместили в Реестр нужную информацию, получить CLSID по ProgID и наоборот легко. Библиотека СОМ предоставляет две функции — CLSIDFromProgID и ProgIDFromCLSID, — которые производят необходимые манипуляции с Реестром:
CLSID clsid;
CLSIDFromProgID(“Helicopter.TailRotor”, &clsid);
Саморегистрация
Каким образом информация о компоненте попадает в Реестр Windows? Так как DLL знает о содержащемся в ней компоненте, она может поместить эту информацию в Реестр. Но, поскольку DLL ничего не делает сама по себе, Вам следует экспортировать следующие две функции:
STDAPI DllRegisterServer();
STDAPI DllUnregisterServer();
STDAPI определен в OBJBASE.H как