- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
220
Как и танграм, СОМ очень проста — и в то же время приложения, которые можно с ее помощью построить, могут быть очень мощными. По-моему, вариация на тему танграма прекрасно подходит для примера программы, которая идеи этой книги объединяет в одно целое.
Программа Tangram
Первоначально я планировал построить эту книгу вокруг одной программы, а не давать разные примеры в каждой главе. Однако отзывы рецензентов быстро сделали очевидной нежизнеспособность такого подхода. СОМ напоминает скелет слона, я имею в виду, приложения. Приложение — это плоть, кожа и мышцы, которые поддерживает скелет. Трудно разглядеть скелет, он скрыт под мышцами и кожей; но, конечно, мышцы «оживляют» скелет, а кожа защищает человека или животное. Как и скелет, СОМ трудно разглядеть в реальном приложении за всем остальным кодом, который заставляет программу делать что-то полезное. Поэтому я решил, чтобы как можно сильнее выделить СОМ, использовать во всех главах книги очень простые примеры.
Тем не менее, я считаю полезным показать новые идеи в конкретном контексте. Для этого и написан Tangram, законченное приложение СОМ для Microsoft Windows. Tangram демонстрирует большинство технологий, представленных в книге, все вместе и в одном приложении. Кроме того, Tangram демонстрирует некоторые OLE, ActiveX и COM интерфейсы, которые я еще не рассматривал.
Tangram в работе
Откомпилированная версия программы находится в каталоге \TANGRAM на прилагаемом к книге диске. Сначала запустите REGISTER.BAT, чтобы зарегистрировать компоненты. После этого можете запускать приложение, дважды щелкнув мышью его значок.
При запуске Tangram выводится диалоговое окно, предоставляющее возможность выбрать один из вариантов работы программы:
!" Окно списка позволяет выбрать «мировой» компонент, который Вы хотите использовать для рисования танграма на экране. Компонент TangramGdiWorld рисует двумерное отображение, а TangramGLWorld — трехмерное. Если библиотека OpenGL в Вашей системе не установлена, то доступен только вариант
TangramGdiWorld.
!" Флажок позволяет выбрать, будет ли «модельный» компонент, представляющий фрагменты танграма, выполняться внутри или вне процесса. Компоненты вне процесса исполняются локально (если только Вы сами не настроите их для удаленного выполнения, как описано в гл. 10).
После запуска Tangram Вы увидите на экране семь фрагментов. С помощью мыши их можно двигать. Щелчок правой кнопки поворачивает фрагмент против часовой стрелки. Если при этом удерживать клавишу Shift, то фрагмент поворачивается в противоположном направлении. Попробуйте поработать с программой, и Вы увидите, какие образы можно с ее помощью сложить!
Детали и составные части
Исходный текст программы Tangram находится на прилагаемом к книге диске в подкаталоге \TANGRAM\SOURCE, там же Вы найдете указания по компоновке и регистрации программы.
Tangram состоит из нескольких компонентов и множества интерфейсов. Названия компонентов и интерфейсов, специфичных для Tangram, имеют префиксы Tangram и ITangram соответственно. Наличие префикса позволяет легко определить, какие интерфейсы относятся к данному примеру.
Чтобы облегчить Вам поиск в Реестре связанных с Tangram интерфейсов и компонентов, все GUID Tangram имеют следующие общие цифры:
B53313xxx-20C4-11D0-9C6C-00A0C90A632C
Основные составляющие Tangram компоненты и интерфейсы приведены в табл. 13-1.
Таблица 13-1 Основные компоненты и интерфейсы программы Tangram
Компонент |
Интерфейсы |
Назначение |
|
|
|
TangramModel |
ITangramModel |
Содержит информацию о форме и положении |
|
ITangramTransform |
отдельного фрагмента |
|
ITangramPointContainer |
|
TangramGdiVisual |
ITangramVisual |
Отображает один фрагмент |
|
ITangramGdiVisual |
|
221
Компонент |
Интерфейсы |
Назначение |
|
|
|
|
ITangramModelEvent |
|
TangramGdiWorld |
ITangramWorld |
Управляет процессом отображения |
|
ITangramGdiWorld |
|
|
ITangramCanvas |
|
TangramCanvas |
ITangramCanvas |
Осуществляет поддержку отображения |
|
|
|
Интерфейсы и компоненты из предыдущей таблицы, имена которых содержат Gdi, имеют эквиваленты с именами, содержащими GL. Версия TangramGdiVisual для OpenGL называется TangramGLVisual. Версии интерфейсов и компонентов для GDI представляют двумерное отображение игрового поля танграма, тогда как версии для OpenGL обеспечивают трехмерное представление.
Следующие разделы кратко описывают основные компоненты, составляющие программу Tangram. Упрощенная схема архитектуры приложения показана на рис. 13-3.
(Подозреваю, Вы подумали, что если таково упрощенное представление, то полное лучше не видеть.)
Клиентский EXE-модуль
Список указателей на модели
pITangramTransform
m_pSelectedVisual
m_pWorld
pCanvas
TangramGdiWorld
Список указателей на фигуры
ITangramWorld
ITangramGdiWorld
TangramCanvas
ITangramCanvas
TangramModel
ITangramModel
ITangramTransform
Указатель на получателя событий
TangramGdiVisual
m_pModel
ITangramVisual
ITangramGdiVisual
ITangramModelEvent
m_pGdiWorld
Рис. 13-3 Схема архитектуры программы Tangram
Клиентский EXE-модуль
Код клиентского EXE-модуля склеивает все компоненты в общее приложение. У клиентского EXE нет интерфейсов, это обычный код на С++ для Win32, хотя клиент и использует MFC, что несколько упростило его программирование. Модуль содержит указатели на управляемые им интерфейсы. Он взаимодействует с
Tangram*World через ITangramWorld, с текущей выбранной фигуркой через ITangramVisual и с TangramModel через ITangramModel и ITangramTransform.
В программе присутствует семь экземпляров компонента TangramModel — по одному на каждый фрагмент танграма. Каждому TangramModel соответствует свой Tangram*Visual. Последний взаимодействует с
TangramModel через интерфейс ITangramModel. Компонент Tangram*World содержит семь Tangram*Visual. Каждый ITangram*World управляет каждым из Tangram*Visual при помощи интерфейса ITangram*Visual.
Tangram*World также агрегирует TangramCanvas, чтобы получить реализацию ITangramCanvas, который используется клиентским EXE.
Компонент TangramModel
TangramModel — это основа программы Tangram. Компонент TangramModel, который я иногда называю просто «модель», — это многоугольник, представляющий один фрагмент танграма. Клиент управляет фрагментом танграма при помощи интерфейсов ITangramModel и ITangramTransform.
222
Интерфейс ITangramModel
ITangramModel инкапсулирует координаты многоугольника, представляющего фрагмент танграма. Программа помещает все фрагменты танграма на виртуальное игровое поле 20x20 и манипулирует ими, используя только координаты в этом поле. Переход координат вершин в виртуальном игровом поле к фигуркам, отображенным на экране, возлагается на компоненты, реализующие ITangramWorld и ITangramVisual.
Интерфейс ITangramTransform
Клиент использует интерфейс ITangramTransform для перемещения и вращения фрагмента танграма. В программе Tangram перемещение выражается через координаты виртуального игрового поля. Поворот задается в градусах.
Интерфейс IConnectionPointerContainer
Это стандартный интерфейс COM/ActiveX. Более подробно он рассматривается далее в разделе «События и точки подключения». Данный интерфейс предоставляет TangramModel гибкий способ информировать соответствующий Tangram*Visual об изменении положения компонента.
Компоненты TangramGdiVisual и TangramGLVisual
Каждый компонент TangramModel имеет соответствующий компонент Tangram*Visual, или просто образ (visual). Компонент TangramGdiVisual использует GDI для вывода двумерного изображения танграмной фигурки. Компонент TangramGLVisual при помощи OpenGL «оживляет» трехмерный образ фигурки. Оба компонента содержат указатель на интерфейс ITangramModel компонента TangramModel. С помощью этого интерфейса компонент Tangram*Visual может получить координаты вершин соответствующего TangramModel и преобразовать их в экранные координаты. Компоненты Tangram*Visual реализуют три интерфейса:
ITangramVisual, ITangram*Visual и ITangramModelEvent.
Интерфейс ITangramVisual
Интерфейс ITangramVisual используется в программе для получения модели, соответствующей данному образу, и для выделения заданной фигурки. Выделение влияет на то, как отображается соответствующий фрагмент танграма.
Интерфейсы ITangramGdiVisual и ITangramGLVisual
Компонент TangramGdiWorld использует ITangramGdiVisual для отображения на экране двумерного представления TangramModel. Компонент TangramGLWorld через ITangramGLVisual взаимодействует с компонентами TangramGLVisual для вывода на экран трехмерных версий TangramModel.
Использование нескольких интерфейсов изолирует клиент от деталей вывода изображений, зависящих от реализации. TangramGdiWorld и TangramGLWorld инкапсулируют детали, отличающие двумерное рисование от трехмерного, и полностью изолируют их от клиента. Клиент может спокойно работать с фрагментами танграма на воображаемом игровом поле 20x20, независимо от того, как на самом деле осуществляется отображение этого поля компонентами Tangram*World и Tangram*Visual.
Если клиент и TangramModel могут игнорировать способ вывода фигурок танграма на дисплей, то Tangram*World и Tangram*Visual должны учитывать роль друг друга в рисовании этих образов. Tangram*World подготавливает экран, на котором рисует каждый из Tangram*Visual. Эти компоненты писались одновременно с учетом взаимодействия в паре. Учитывая, как определены интерфейсы, практически невозможно написать один компонент, не написав другой. Здесь Вы можете рассматривать сочетание этих двух классов COM как один «компонент».
ITangramModelEvent
Компоненту ITangram*Visual необходимо знать об изменениях координат вершин соответствующего ITangramModel. Для этого ITangramModel определяет интерфейс событий с именем ITangramModelEvent. Всякий раз, когда изменяется положение вершин, TangramModel вызывает ITangramModelEvent::OnChangeModel для всех компонентов, ожидающих этого события (в данном случае таким компонентом является только соответствующий образ). Мы рассмотрим события позже в разделе «События и точки подключения».
Компоненты TangramGdiWorld и TangramGLWorld
Каждый компонент Tangram*Visual содержится в соответствующем компоненте Tangram*World. Tangram*World отвечает за подготовку дисплея, на котором будут рисовать TangramVisual. Он также отвечает за перерисовку