- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
80
GUID
ВСША всем обычным летательным аппаратам FAA присваивает N-номер (N number), который идентифицирует самолет, — как номерной знак идентифицирует Вашу машину. Этот номер уникален для каждого самолета и используется пилотом в переговорах с авиадиспетчерами. В этом разделе мы обсудим GUID, которые являются такими «опознавательными знаками» компонентов и интерфейсов.
Вгл. 3 я предложил Вам представлять себе IID как константу, идентифицирующую данный интерфейс. Однако, как Вы могли видеть из определения IID_IX, IID — это константа особого рода:
extern const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
На самом деле IID представляет собой тип, определенный как структура длинной 128 битов (16 байтов) под названием GUID. GUID — аббревиатура Globally Unique IDentifier (глобально уникальный идентификатор;
произносится как «гуид» — как первая часть в слове geoduck1 и последняя — в druid).
Зачем нужен GUID?
Почему мы используем GUID, а не длинное целое (long integer)? С помощью длинных целых можно однозначно задать 232 интерфейсов. Я сильно сомневаюсь, что большее их число когда либо понадобится. Однако настоящая проблема не в том, сколь много интерфейсов мы сможем однозначно задать, но в том, как гарантировать уникальность идентификатора интерфейса. Если два идентификатора совпадают, клиент легко может получить от QueryInterface неверный указатель на интерфейс. Проблема усложняется тем, что компоненты создаются разработчиками по всему земному шару. Если Сара в Ако и Линн в Таксоне разрабатывают новые интерфейсы СОМ, то как им удостовериться, что идентификаторы интерфейсов не будут конфликтовать? Можно было бы договориться о чем-нибудь вроде N-номеров летательных аппаратов и учредить некое центральное агентство, наподобие FAA, для выделения идентификаторов. Централизованная организация подходит для относительно ограниченного числа летательных аппаратов; но я сомневаюсь, что какое-нибудь агентство смогло бы столь же успешно, как FAA, работать с тем количеством интерфейсов, которое необходимо для средней программы.
Для GUID есть более удачное решение. Уникальный GUID можно сгенерировать программно, без какой-либо координирующей организации. Microsoft Visual C++ предоставляет для генерации GUID две программы — утилиту командной строки UUIDGEN.EXE и диалоговую программу на VC++, GUIDGEN.EXE. Если я сейчас запущу UUIDGEN.EXE, то получу строку, представляющую некоторый GUID:
{166769E1-88E8-11CF-A6BB-0080C7B2D692}
При всяком новом запуске UUIDGEN получается иной GUID. Если Вы запустите UUIDGEN на своей машине, то получите GUID, отличный от моего. Если миллионы (я надеюсь) людей, читающих эту книгу, сейчас запустят UUIDGEN, они получат миллион разных GUID.
Исходный текст GUIDGEN.EXE можно найти в примерах программ Microsoft Visual C++. Но я и так могу сказать Вам, как работает эта программа: она просто вызывает функцию библиотеки СОМ Microsoft CoCreateGuid, которая вызывает функцию RPC UuidCreate.
Теория GUID
GUID по определению уникален «в пространстве и во времени». Для обеспечения «географической» уникальности каждый GUID использует 48-битовое значение, уникальное для компьютера, на котором он генерируется. Обычно в качестве такого значения берется адрес сетевой платы. Такой подход гарантирует, что любой GUID, полученный на моем компьютере, будет отличаться от любого, сгенерированного на Вашем компьютере. Для тех компьютеров, в которых не установлен сетевой адаптер, используется другой алгоритм генерации уникальных значений. В каждом GUID 60 битов отведено для указания времени. Туда заносится число 100-наносекундных интервалов, прошедших с 00:00:00:00 15 октября 1582 года. Используемый в настоящее время алгоритм генерации GUID начнет выдавать повторяющиеся значения примерно в 3400 году. (Я подозреваю, что очень немногие из нынешних программ, за исключением некоторых на Фортране, еще будут использоваться в 3400 году; но я верю, что к этому времени уже выйдет Windows 2000.)
GUID придумали толковые ребята из Open Software Foundation (OSF); правда, они использовали термин UUID (Universally Unique IDentifiers — вселенски уникальные идентификаторы). UUID разработали для использования в среде распределенных вычислений (DCE, Distributed Computing Environment). Вызовы удаленных процедур (RPC) DCE используют UUID для идентификации вызываемого, т.е. практически затем же, зачем и мы.
1 Нормальные люди произносят это как «gooey duck». Программисты же говорят «GUI duck».
81
Дополнительно о генерации UUID или GUID можно прочитать в CAE Specification X/Open DCE: Remote Procedure Call.
Объявление и определение GUID
Поскольку размер GUID велик (128 битов), не хотелось бы, чтобы они повторялись в нашем коде повсюду. В гл. 5 GUID определялись в файле GUIDS.CPP примерно так:
extern const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
Объявлены они были в файле IFACE.H так:
extern “C” const IID IID_IX;
Ввести для GUID два файла, один с определениями, а другой с объявлениями — изрядная морока. Чтобы определить и объявить GUID одним оператором, используйте макрос DEFINE_GUID, который определен в OBJBASE.H. Для использования DEFINE_GUID генерируйте GUID с помощью GUIDGEN.EXE. Эта программа генерирует GUID в различных форматах — выберите второй из них. Этот формат используется в следующем примере.
// {32bb8320-b41b-11cf-a6bb-0080c7b2d682} DEFINE_GUID(<<name>>,
{0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
Вставьте сгенерированный GUID в заголовочный файл. Замените <<name>> идентификатором, используемым в Вашем коде, — например, IID_IX:
// {32bb8320-b41b-11cf-a6bb-0080c7b2d682} DEFINE_GUID(IID_IX,
{0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
В соответствии с определением в OBJBASE, DEFINE_GUID генерирует что-то вроде:
extern “C” const GUID IID_IX;
Однако, если после OBJBASE.H включить заголовочный файл INITGUID.H, макрос DEFINE_GUID будет раскрываться так:
extern “C” const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
Механизм работы этих заголовочных файлов представлен на рис. 6-2. Заголовок IFACE.H использует макрос DEFINE_GUID для объявления IID_IX. Идентификатор IID_IX определен в файле GUIDS.H. Он определен там потому, что заголовочный файл INITGUID.H включен после OBJBASE.H и перед IFACE.H. С другой стороны, в файле CMPNT.CPP IID_IX объявлен но не определен, поскольку заголовочный файл INITGUID.H здесь не включен.
Так как я старался сделать примеры в книге максимально ясными, то DEFINE_GUID я в них не использовал, но явно определял используемые GUID.
GUIDS.CPP |
|
|
||
#include <objbase.h> |
|
IID_IX определяется, |
||
#include |
<initguid.h> |
|
|
так как INITGUID.H |
|
|
|||
#include |
"Iface.h" |
|
включен |
IFACE.H
DEFINE_GUID(IID_IX, 0x32bb8320, 0xb41b, 0x11cf,
0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82);
CMPNT.CPP |
|
|
||
#include |
<objbase.h> |
|
IID_IX объявляется, |
|
#include |
"Iface.h" |
|
|
так как INITGUID.H |
|
|
|||
(здесь идет код) |
|
не включен |
||
|
|
|
|
|
Рис. 6-2 Включение INITGUID.H заставляет макрос DEFINE_GUID определить GUID