- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
29
Интерфейсы не всегда наследуются
Класс CA наследует поддерживаемые им интерфейсы. СОМ не требует, чтобы класс, реализующий интерфейсы, наследовал их, поскольку клиент никогда не видит иерархию наследования в компоненте СОМ. Наследование интерфейсов — это в чистом виде деталь реализации. Вместо того, чтобы собирать все интерфейсы в одном классе, Вы можете реализовать их отдельно и использовать указатели на соответствующие классы. Крейг Брокшмидт использует такой метод в своей книге Inside OLE. Мы будем использовать для реализации всех интерфейсов один класс, поскольку этот метод проще и легче для восприятия; он также делает программирование для СОМ на С++ более естественным.
Множественные интерфейсы и множественное наследование
Компоненты могут поддерживать сколь угодно много интерфейсов. Для поддержки нескольких интерфейсов мы используем множественное наследование. В листинге 2-1 CA является производным от двух интерфейсов IX и IY, которые он поддерживает. Благодаря поддержке множественных интерфейсов компонент можно рассматривать как набор интерфейсов.
Это определяет рекурсивно-вложенную природу компонентной архитектуры (см. рис. 2-3). Интерфейс — это набор функций, компонент — набор интерфейсов, а система — набор компонентов. Некоторые считают интерфейсы эквивалентами функциональных возможностей и при добавлении к компоненту новых интерфейсов говорят о появлении новых возможностей. Я же предпочитаю рассматривать интерфейсы как различные варианты поведения компонента. Набор интерфейсов соответствует набору таких вариантов.
Компонент 1 |
Компонент 2 |
|
Компонент n |
||||
|
Fx1 |
|
Fx1 |
|
|
Fx1 |
|
IX1 |
Fx2 |
IX1 |
Fx2 |
|
IX1 |
Fx2 |
|
... |
... |
|
... |
||||
|
Fxn |
|
Fxn |
|
|
Fxn |
|
|
Fx1 |
|
Fx1 |
|
|
Fx1 |
|
IX2 |
Fx2 |
IX2 |
Fx2 |
... |
IX2 |
Fx2 |
|
... |
... |
... |
|||||
|
|
|
|
||||
|
Fxn |
|
Fxn |
|
|
Fxn |
|
|
... |
|
... |
|
|
... |
|
|
Fx1 |
|
Fx1 |
|
|
Fx1 |
|
IXn |
Fx2 |
IXn |
Fx2 |
|
IXn |
Fx2 |
|
... |
... |
|
... |
||||
|
Fxn |
|
Fxn |
|
|
Fxn |
Рис. 2-3 Система компонентов — это набор компонентов, из которых каждый поддерживает набор интерфейсов, из которых каждый содержит набор функций
Конфликт имен
Поскольку компонент может поддерживать несколько интерфейсов, легко возникает конфликт имен функций этих интерфейсов. Действительно, СОМ нет до этого дела. Интерфейсы СОМ отвечают двоичному стандарту; клиент подключается к интерфейсу не по имени самого интерфейса или его функций. Клиент обращается к функции интерфейса по ее адресу в блоке памяти, представляющем интерфейс. Структура этого блока памяти будет рассмотрена в конце данной главы. Техника изменения имен интерфейсов и функций обсуждается в гл. 8.
Другое решение проблемы конфликта имен — отказ от множественного наследования. Классу, управляющему компонентом, нет необходимости наследовать каждый интерфейс; он может содержать указатели на другие классы, которые реализуют отдельные интерфейсы.
Конфликт имен интерфейсов встречался бы реже, если бы все разработчики СОМ следовали простому соглашению. Начинайте имена интерфейсов и функций с названия Вашей фирмы или программного продукта. Например, вместо IFly для продукта Xyz можно было бы использовать IXyzFly.
Теперь, став экспертами в реализации интерфейсов, снова погрузимся в теорию.
Теория интерфейсов, часть II
Перед реализацией интерфейсов я обещал, что позже дам некоторые дополнительные сведения из теории. Я Вас не обманывал. В этом разделе мы рассмотрим три вопроса: неизменность интерфейсов СОМ, полиморфизм и наследование интерфейсов.