- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
133
Внешний компонент
Инструмент
преобразования
IToolInfo
ISetColors
Внутренний компонент
Алгоритм
преобразования
IMorph
IColors
Рис. 8-7 Интерфейс IToolInfo — это метаинтерфейс, вероятность конфликта которого с интерфейсами внутреннего компонента мала. ISetColors не является метаинтерфейсом и действует в области компетенции внутреннего компонента.
ISetColors конфликтует с интерфейсом IColors внутреннего компонента.
Взаимосвязанные пары
Другой способ избежать конфликтов интерфейсов — дать клиенту дополнительные сведения о внешнем компоненте. Если клиент и внешний компонент разрабатывались совместно, первый знает, какие интерфейсы реализованы вторым, и может использовать их вместо тех, что потенциально могут реализовываться внутренним компонентом. Если совместно разрабатывались внешний и внутренний компоненты, их можно спроектировать так, чтобы конфликтных интерфейсов не было. Однако подобные взаимосвязанные пары требуют, чтобы Вы контролировали не только внешний компонент, но также и клиент либо внутренний компонент.
Агрегирование и включение в реальном мире
Мы познакомились с тем, как обеспечить повторное применение компонентов при помощи включения и агрегирования. Однако при повторном применении компонентов не все так гладко. Поскольку внешний компонент — всего лишь клиент внутреннего, он может специализировать метод последнего, вызывая другие методы только перед ним или после него. Внешний компонент не может вставить новый вариант поведения в середину такого метода. Кроме того, поскольку метод может изменять внутреннее состояние компонента, к которому у клиента нет доступа, Вы не можете заменить всю реализацию отдельного метода интерфейса.
Например, если Вы используете некоторый интерфейс для открытия файла, для закрытия этого файла Вы обязаны использовать тот же самый интерфейс — поскольку Вам ничего не известно о реализации интерфейса или о какой-либо из его функций. Повторю, у внешнего компонента нет никакого дополнительного доступа или возможностей по сравнению с обычным клиентом. Внешний компонент обязан использовать внутренний совместимым и корректным образом, как и любой другой клиент.
Для устранения этих недостатков компонент можно спроектировать так, чтобы способствовать повторной применимости. То же самое верно и для классов С++, которые трудно, если не невозможно, расширять или специализировать, если они не спроектированы надлежащим образом. Классы С++, разработанные с учетом последующей настройки, имеют «защищенные» методы, которые используются производным классом для получения информации о внутреннем состоянии базового класса. Компоненты СОМ могут использовать интерфейсы для того, чтобы облегчить свое включение или агрегирование.
Предоставление информации о внутреннем состоянии
ВСОМ все делается через интерфейсы. Следовательно, внутренний компонент может предоставить информацию о своем внутреннем состоянии, добавив новый интерфейс. Этот интерфейс мог бы предоставлять внешнему компоненту информацию или сервисы, помогающие настроить внутренний компонент (рис. 8-8).
Винтерфейсах внутреннего состояния нет ничего загадочного. Это нормальные интерфейсы, которые предоставляют контролируемый доступ к внутреннему состоянию компонента, чтобы упростить настройку, или чтобы настройка вообще стала возможна. Обычные клиенты также при желании могут использовать интерфейсы внутреннего состояния, но в большинстве случаев эти интерфейсы будут им бесполезны.
134
Внешний компонент |
|
IX |
|
|
Внешний компонент использует |
Реализация |
IInternalState для получения |
внешнего компонента |
дополнительной информации о |
|
состоянии внутреннего |
Внутренний компонент |
|
IInternalState |
|
IY |
IInternalState предоставляет |
|
внешнему компоненту |
IZ |
информацию о внутреннем |
состоянии данного компонента |
Рис. 8-8 Внутренний компонент может упростить свою настройку, предоставив интерфейсы, которые дают внешнему компоненту доступ к его внутреннему состоянию
Интерфейсы, предоставляющие внешнему компоненту такого рода информацию, играют в СОМ важную роль. Компоненты СОМ обычно состоят из множества небольших интерфейсов. Внутри себя реализации этих интерфейсов взаимозависимы, так как используют общие переменные-члены и другие аспекты внутреннего состояния. Это означает, что Вы не можете взять один из интерфейсов компонента и использовать его сам по себе, потому что этот интерфейс может зависеть (в управлении некоторым аспектом внутреннего состояния компонента) от информации или состояния другого интерфейса (рис. 8-9). Повторю еще раз, внешний компонент — просто клиент внутреннего. У него нет никаких особых возможностей.
Независимо реализованные интерфейсы
|
|
|
|
|
|
|
|
IX |
|
Реализация IX |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IY |
|
Реализация IY |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IZ |
|
Реализация IZ |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
Реализации интерфейсов редко бывают независимыми
Интерфейсы с общей реализацией |
||
IX |
|
|
IY |
Реализация |
|
IX, IY, IZ |
||
|
||
IZ |
|
|
Обычно реализации интерфейсов |
||
|
взаимосвязаны |
Рис. 8-9 Клиент рассматривает интерфейсы как независимые сущности. Однако обычно интерфейсы используют общие детали реализации. Это еще более затрудняет расширение или специализацию компонентов. Добавление интерфейсов, предоставляющих взгляд на компонент изнутри, может упростить его настройку.
Моделирование виртуальных функций
Дополнительные интерфейсы могут не только предоставить эквивалент СОМ для защищенных функций-членов С++, но и позволить интерфейсам замещать виртуальные функции. Во многих случаях виртуальные функции используются как функции обратного вызова (callback). Базовый класс может вызывать виртуальную функцию до, во время или после некоторой операции, чтобы дать производному классу возможность модифицировать ее выполнение. Компоненты СОМ могут делать то же самое, определив интерфейс настройки (customization interface). Компонент не реализует такой интерфейс, а, наоборот, вызывает его. Клиенты, желающие настроить компонент для своих нужд, реализуют интерфейс настройки и передают указатель на него компоненту. Эту технику клиенты могут применять, и не используя включение или агрегирование (рис. 8-10).
Клиент
Клиент использует IX
ICustomize
Компонент
IX
Компонент вызывает
ICustomize, чтобы дать клиенту возможность настройки поведенияIX
Рис. 8-10 Компонент определяет исходящий интерфейс, который он вызывает для своей настройки