- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
72
{
virtual void __stdcall Fx() = 0; };
interface IY : IUnknown
{
virtual void __stdcall Fy() = 0; };
interface IZ : IUnknown
{
virtual void __stdcall Fz() = 0; };
// Предварительные объявления GUIDs extern "C"
{
extern const IID IID_IX; extern const IID IID_IY; extern const IID IID_IZ;
}
Листинг 5-5 Объявления интерфейсов
Как видите, клиент и компонент по-прежнему используют интерфейсы IX, IY и IZ. Идентификаторы этих интерфейсов объявлены в конце IFACE.H. IID будут обсуждаться в следующей главе. Определения идентификаторов интерфейсов находятся в файле GUIDS.CPP, который показан в листинге 5-6.
GUIDS.CPP
//
// GUIDs.cpp – Идентификаторы интерфейсов
//
#include <objbase.h>
extern "C"
{
// {32bb8320-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IX =
{0x32bb8320, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
// {32bb8321-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IY =
{0x32bb8321, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
// {32bb8322-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IZ =
{0x32bb8322, 0xb41b, 0x11cf,
{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};
// extern необходим, чтобы для констант C++ была выделена память
}
Листинг 5-6 Идентификаторы интерфейсов определены в GUIDS.CPP. С этим файлом компонуются и клиент, и компонент.
Это были детали реализации компонента в DLL. Давайте немного поиграем с такими компонентами.
Связки объектов
Теперь Вы можете поиграть с компонентами и посмотреть, как они динамически компонуются. В каталоге CHAP05 содержится код трех клиентов; это Клиент 1, Клиент 2 и Клиент 3. Здесь же находится код трех компонентов, которые мы обозначим как Компонент 1, Компонент 2 и Компонент 3. Код в IFACE.H определяет три интерфейса: IX, IY и IZ. Клиент 1 и Компонент 1 поддерживают интерфейс IX. Клиент 2 и Компонент 2 поддерживают интерфейсы IX и IY. Клиент 3 и Компонент 3 поддерживают все три интерфейса. В табл. 5-1 показан набор интерфейсов, поддерживаемых каждым клиентом и компонентом.
73
Таблица 5-1 Эта таблица показывает, какие интерфейсы поддерживаются каждым клиентом и компонентом
|
IX |
IY |
IZ |
|
Клиент 1 |
# |
|
|
Компонент 1 |
Клиент 2 |
# |
# |
|
Компонент 2 |
Клиент 3 |
# |
# |
# |
Компонент 3 |
Все клиенты и компоненты компилируются по команде
nmake –f makefile
Каждый из клиентов при запуске спрашивает, какой компонент он должен использовать. Введите имя компонента и нажмите <Enter>. Клиент с соответствующим партнером. Затем он запросит у компонента каждый известный ему интерфейс. Если компонент поддерживает интерфейс, клиент вызовет функцию этого интерфейса. В противном случае клиент сломается.
Я хотел просто посмотреть, не заснули ли Вы. Клиент не сломается, а компонент напечатает симпатичное маленькое сообщение о том, что не поддерживает интерфейс. Ниже приведен пример работы Клиента 2 и Компонентом 2 и Клиента 3 с Компонентом 1.
C:\client2
Введите имя файла компонента [Cmpnt?.dll]: cmpnt2.dll
Клиент 2: Получить указатель на IUnknown Компонент 2: Возвратить указатель на IUnknown Клиент 2: Получить интерфейс IX
Компонент 2: Возвратить указатель на IX Клиент 2: IX получен успешно
Fx
Клиент 2: Получить интерфейс IY Компонент 2: Возвратить указатель на IY Клиент 2: IY получен успешно
Fy
Клиент 2: Освободить интерфейс IUnknown Компонент 2: Ликвидировать себя
C:\client3
Введите имя файла компонента [Cmpnt?.dll]: cmpnt1.dll
Клиент 3: Получить указатель на IUnknown Клиент 3: Получить интерфейс IX Компонент 1: Возвратить указатель на IX Клиент 3: IX получен успешно
Fx
Клиент 3: Получить интерфейс IY Компонент 1: Интерфейс не поддерживается Клиент 3: Не могу получить интерфейс IY Клиент 3: Получить интерфейс IZ Компонент 1: Интерфейс не поддерживается Клиент 3: Не могу получить интерфейс IZ Клиент 3: Освободить интерфейс IUnknown Компонент 1: Ликвидировать себя
Компонент 2 реализует все интерфейсы, нужные клиенту 2. Компонент 1 реализует только IX, тогда как Компоненту 3 нужны все три интерфейса: IX, IY и IZ. Попробуйте другие комбинации компонентов и клиентов.
Вам понравилось? По-моему, это замечательно. Мы успешно создали архитектуру, которая позволяет подключать друг к другу компоненты и клиенты во время выполнения. Подозреваю, что Вы хотите пойти это отметить, так что я закругляюсь.
Негибкое связывание, резюме
В этой главе мы добавили нашим компонентам одно свойство — динамическую компоновку. Поместив компоненты в DLL, мы можем заменять их во время выполнения. Как Вы видели из примеров, один клиент может легко работать с разными компонентами без перекомпоновки или перекомпиляции. Динамическая компоновка в сочетании с хорошо спроектированными интерфейсами может обеспечить создание невероятно гибких приложений, которые будут эволюционировать с течением времени.
74
Как бы ни были гибки наши компоненты, по-прежнему остается один момент, в котором гибкости не хватает, — момент создания. CallCreateInstance требует, чтобы клиент знал имя DLL, в которой реализован компонент. Имя DLL — это деталь реализации, которую нам хотелось бы скрыть от клиента. Компонент должен быть способен изменить имя DLL, в которую он погружен, и не повлиять на клиентов. Хотелось бы также поддерживать в одной DLL несколько компонентов. Эти вопросам и посвящены следующие две главы.