- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
86
#define STDAPI EXTERN_C HRESULT STDAPICALLTYPE
что раскрывается в
extern “C” HRESULT __stdcall
С помощью программы REGSVR32.EXE эти функции можно вызвать для регистрации компонента. Эта вездесущая утилита, вероятно, уже есть на Вашем компьютере. В примерах программ ряда последующих глав этой книги make-файлы будут вызывать REGSVR32.EXE для регистрации соответствующих компонентов.
Большинство программ установки вызывают DllRegisterServer в процессе своей работы. Для этого нужно просто загрузить DLL с помощью LoadLibrary, получить адрес функции с помощью GetProcAddress и потом, наконец, вызвать функцию.
Реализация DllRegisterServer
Реализация DllRegisterServer — простой код обновления Реестра. Win32 содержит множество функций, добавляющих и удаляющих разделы Реестра. Для регистрации наших компонентов и удаления их из Реестра понадобятся только шесть функций:
RegOpenKeyEx
RegCreateKeyEx
RegSetValueEx
RegEnumKeyEx
RegDeleteKey
RegCloseKey
Об этих функциях много написано в других книгах, поэтому я не собираюсь детально рассматривать их здесь. Чтобы использовать эти функции, включите в Ваш исходный файл WINREG.H и WINDOWS.H и скомпонуйте программу с ADVAPI32.LIB. Увидеть эти функции в действии Вы сможете в файлах REGISTRY.H и REGISTRY.CPP из примера следующей, седьмой главы.
Категории компонентов
Минималистский взгляд на Реестр Windows состоит в том, что это длинный список CLSID, с каждым из которых связано имя файла. Клиент может просмотреть эти CLSID и выбрать подходящий компонент. Но как клиент определяет, какой именно компонент следует использовать? Один из вариантов — вывести список дружественных имен компонентов и предоставить пользователю выбирать. Однако пользователю вряд ли понравится, если, выбрав компонент из длинного списка, он в итоге узнает, что тот не работает. Загружать каждый из компонентов, упомянутых в Реестре, и запрашивать у них необходимые нам интерфейсы — слишком затяжное мероприятие. Надо как-то уметь определять, поддерживает ли компонент нужные интерфейсы, до создания экземпляра этого компонента.
Решение этой проблемы дают категории компонентов (component categories). Категория компонентов — это набор интерфейсов, которым присвоен CLSID, называемый в данном случае CATID. Компоненты, реализующие все интерфейсы некоторой категории, могут зарегистрироваться как члены данной категории. Это позволяет клиентам более осмысленно выбирать компоненты из реестра, рассматривая только те, которые принадлежат к некоторой категории.
Категория компонентов — тоже своего рода договор между компонентом и клиентом. Регистрируя себя в некоторой категории, компонент тем самым гарантирует, что поддерживает все входящие в категорию интерфейсы. Категории могут использоваться для типизации компонентов. Использование категорий аналогично использованию абстрактных базовых классов в С++. Абстрактный базовый класс — это набор функций, которые производный класс обязан реализовать; поэтому можно сказать, что производный класс — конкретная реализация данного абстрактного базового класса. Категория компонентов — набор интерфейсов, которые должны быть реализованы компонентом, чтобы тот относился к данной категории. Компонент, принадлежащий категории, — конкретная реализация данной категории.
Компонент может входить в произвольное число категорий. Компонент не обязан поддерживать исключительно те интерфейсы, которые определены категорией; он может поддерживать любой интерфейс в дополнение к ним.
Одно из применений категорий — задание набора интерфейсов, которые компонент обязан поддерживать. Альтернативой служит задание набора интерфейсов, которые компонент требует от своего клиента. Компоненту для нормальной работы могут потребоваться от клиента некоторые сервисы. Например, трехмерному графическому объекту для работы может потребоваться определенная графическая библиотека (graphic engine).