- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
226
TangramGdiWorld |
TangramGdiVisual |
|
Список указателей на образы |
ITangramGdiVisual |
|
слабая ссылка |
|
|
ITangramGdiWorld |
m_pGdiWorld |
|
подкомпонент |
||
|
||
|
сильные ссылки |
Рис. 13-7 Ссылочный цикл можно разорвать, используя подкомпоненты, поддерживающие слабые ссылки на своих родителей
События и точки подключения
До этого момента мы использовали только однонаправленную связь, когда клиент управляет компонентом. Но и компонент может выступать в качестве клиента и управлять другим компонентом. За исключением агрегирования, компоненты в этой книге никогда не имели указателя на свой клиент. В Tangram ситуация иная.
В предыдущем разделе мы видели, что у TangramGdiVisual имеется обратный указатель на TangramGdiWorld. Одно из самых распространенных применений обратного указателя — уведомление клиента о различных событиях. Как мы видели в предыдущем разделе, проще всего информировать клиент о событии при помощи слабой ссылки. Но более искусный метод состоит в том, чтобы использовать подкомпонент со слабой ссылкой на один из компонентов.
При разработке управляющих элементов ActiveX (OLE) потребовался универсальный и гибкий механизм обработки событий. В качестве решения были использованы точки подключения (connection points). Точка подключения похожа на электрический разъем. Клиент реализует интерфейс, который подключается к точке подключения. Затем компонент вызывает реализованный клиентом интерфейс. Такие интерфейсы называются
исходящими (outgoing) интерфейсами, или интерфейсами источника (source). В IDL для обозначения исходящего интерфейса используется атрибут source. (Пример можно найти в файле \TANGRAM\SOURCE\MODEL\MODEL_C.IDL.) Интерфейс называется исходящим, потому что в данном случае компонент вызывает клиент. Название «интерфейс источника» обусловлено тем, что компонент служит источником вызовов этого интерфейса.
Давайте рассмотрим очень простую схему обратного вызова. На рис. 13-8 TangramModel вызывает интерфейс ITangramModelEvent, реализованный компонентом TangramGdiVisual, однако во избежание ссылочного цикла он реализуется подкомпонентом, который перенаправляет вызовы компоненту TangramGdiVisual (Подобную реализацию мы уже встречали в разделе «Циклический подсчет ссылок».)
На рис. 13-8 TangramModel является источником вызовов интерфейса ITangramModelEvent. Подразумевается, что у ITangramModelEvent имеется функция, инициализирующая m_pEvents. Это простое решение, и оно будет работать. Но его нельзя назвать слишком гибким. Во-первых, у клиента нет стандартного способа определить, какие события поддерживаются компонентом. Во-вторых, поддерживается только один интерфейс событий. В- третьих, TangramModel может посылать события только одному клиенту. Во многих случаях необходимо информировать о произошедших событиях несколько клиентов. Эта проблема решается с помощью точек подключения.
TangramGdiWorld
m_pITangramVisual
ITangramModelEvent
ITangramModelEvent
Источник событий
TangramGdiVisual
ITangramGdiVisual
m_pGdiWorld
Получатель событий
Рис. 13-8 Простая схема обратного вызова, которая не используется в программе Tangram
Первую проблему решает интерфейс IConnectionPointContainer. Он содержит две функции: FindConnectionPoint и EnumConnectionPoints. Первая принимает IID исходящего интерфейса и возвращает указатель точки поключения для этого интерфейса. EnumConnectionPoints возвращает объект, который перечисляет все точки подключения, поддерживаемые компонентом. Это удовлетворяет второму требованию, поддержке компонентом более одного исходящего интерфейса. Перечислители, кстати, мы рассмотрим в следующем разделе.
Точка подключения — это объект, который реализует интерфейс IConnectionPoint. Каждому исходящему интерфейсу соответствует одна точка подключения. У каждой точки подключения может быть несколько
227
получателей. IConnectionPoint::EnumConnections возвращает указатель на IEnumConnections объекта, который перечисляет все подключения. Каждому получателю также может соответствовать несколько источников, но соответствующая реализация — уже задача получателя.
TangramGdiWorld |
TangramGdiVisual |
CEnumConnec- |
|
|
|
m_pITangramVisual |
IConnectionPointContainer |
tionPoints |
ITangramModelEvent |
|
IEnumConnec- |
|
tionPoints |
|
|
|
|
|
Список ConnectionPoint |
|
CTangramModelEventSink |
|
CEnumCon- |
|
nections |
|
|
|
|
ITangramModelEvent |
IConnectionPointContainer |
IEnumCon- |
|
|
|
|
|
nections |
Список ConnectionPoint
Рис. 13-9 Архитектура точек подключения
На рис. 13-9 изображена архитектура точек подключения TangramModel. TangramGdiVisual использует
IConnectionPointContainer, чтобы найти IConnectionPoint, соответствующий IID_ITangramModelEvent. Компонент
TangramGdiVisual передает свой интерфейс функции IConnectionPoint::Advise. Это сообщает точке подключения о том, что TangramGdiVisual хочет получать уведомления о событиях. Для реализации точки подключения TangramModel использует несколько простых объектов СОМ. Эти объекты создаются с помощью оператора new С++, у них нет CLSID, и они не зарегистрированы в Реестре Windows. На рис. 13-9 эти объекты изображены прерывистыми линиями. Они реализуют перечислители для набора точек подключения, набор подключений и сам компонент-точку подключения. У TangramModel есть только одна точка подключения, но тем не менее реализован объект, поддерживающий интерфейс IEnumConnectionPoints. Перечислители будут рассматриваться в следующем разделе.
Представленная архитектура точек подключения (рис. 13-9) довольно сложна. Каждая точка подключения — это отдельный объект; кроме того, присутствуют два перечислителя. Но дополнительная сложность дает большую гибкость.
IEnumXXX
Наборы указателей на интерфейсы и других данных весьма важны в компонентных архитектурах. Поэтому в СОМ определен стандартный шаблон для перечисления содержимого набора. Здесь нет стандартного интерфейса, так как все версии шаблона работают с данными разных типов. Шаблон перечислителя определен как IEnumXXX, который имеет функции Reset, Next, Skip и Clone. Два примера такого интерфейса мы видели в предыдущем разделе — IEnumConnectionPoints и IEnumConnections. В гл. 6 для перечисления доступных категорий мы фактически использовали интерфейс IEnumCATEGORYINFO.
Метод Next интерфейса перечислителя возвращает элементы набора. Одна из интересных особенностей этого метода — то, что Вы можете за один раз получить из набора любое число элементов. Вместо того, чтобы копировать по одному элементу за раз, метод Next позволяет задать количество копируемых элементов. Это значительно повышает производительность при работе с удаленными компонентами, так как сокращает число циклов обмена данными по сети.
Использовать и реализовывать перечислители легко. Самое сложное — не забыть вызывать AddRef и Release для всех перечисляемых указателей на интерфейсы. Если Вы реализуете метод Next, следует вызвать для указателей на интерфейсы AddRef, прежде чем возвращать их клиенту. Если же Вы используете метод Next, то необходимо вызывать для возвращенных им указателей Release.
Фиксируют ли перечислители мгновенное состояние, или они «живые»? Обычно фиксируют, как, например, IEnumConnections и IEnumConnectionPoints. Объект-перечислитель, который Вы получаете в результате вызова IConnectionPoint::EnumConnections, представляет собой «мгновенный снимок» текущих подключений к данной точке. Если набор подключений изменился (из-за того, что другой клиент вызвал Advise для добавления или Unadvise для удаления подключения), Ваш мгновенный снимок обновлен не будет.
228
Основа COM — стандартные интерфейсы
Как я уже неоднократно повторял, СОМ основана на интерфейсах. Чем больше компонентов используют одни и те же интерфейсы, тем больше вероятность полиморфного использования компонентов. Многие интерфейсы уже определены СОМ, OLE, управляющими элементами ActiveX, документами ActiveX и Автоматизацией. Разработчику компонента СОМ следует изучить эти уже существующие интерфейсы. Даже если Вы решите не использовать их в своем приложении, то многое узнаете о создании с их помощью гибких компонентных архитектур.
У-у-ф!
Итак, мы подошли к концу. Вы знаете, как создавать интерфейсы СОМ на С++, реализовывать IUnknown и IClassFactory и регистрировать свои компоненты в Реестре Windows. Вам также известно, как создавать приложения из компонентов, включающих и агрегирующих другие компоненты. Вы знаете, как упростить себе жизнь с помощью классов С++ и smart-указателей. Вы также умеете описывать свои интерфейсы в файлах IDL, чтобы автоматически генерировать библиотеки маршалинга и библиотеки типа. Реализация IDispatch — это простой процесс, состоящий в использовании ITypeInfo. Наконец, Вы мастерски умеете создавать компоненты, реализующие модель разделенных потоков.
Если Вы решите написать компонент СОМ, то, учитывая Ваши знания, единственным недостающим ингредиентом будут конкретные интерфейсы СОМ. Вы знаете, как реализовать интерфейс. Теперь Вам нужно либо разработать собственный интерфейс, либо найти стандартный и реализовать его. Microsoft уже разработаны сотни интерфейсов для технологий ActiveX, DirectX и OLE. Управляющий элемент ActiveX — это просто реализация набора интерфейсов. Документ ActiveX — также набор интерфейсов с их реализациями. Управляющие элементы и документы ActiveX используют много общих стандартных интерфейсов.
Реализация интерфейсов ActiveX, DirectX и OLE — непростая задача. Однако все это, как говорится, детали реализации. Проблема заключается не в СОМ, ведь после этой книги Вы стали настоящим экспертом по СОМ. (Пришлите мне свое имя и адрес на обороте стодолларовой купюры и получите бесплатный сертификат, подтверждающий Ваши знания.) Если Вы достаточно долго поиграете с примером Tangram, то станете и танграмным мастером. Наслаждайтесь!