- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
105
Клиент |
|
|
|
|
|
|
DLL |
|
|
|
|
|
|||||||||
ВызываетCoCreateInstance |
|
|
DllGetClassObject |
|
|
|
|
|
|
|
|
CLSID_1 |
|
&CreateFunction_1 |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
CLSID_2 |
|
&CreateFunction_2 |
||||||
|
|
|
|
|
|||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Фабрика класса 1 |
|
|
|
|
|
|
|
|
CLSID_n |
&CreateFunction_n |
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Компонент 1 |
|
|
|
|
|
|
|
|
|
|
|
|
Компонент n |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
CreateFunction_1 |
|
|
|
|
|
|
|
|
|
|
|
|
CreateFunction_n |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 7-4 Простая реализация фабрики класса может обслуживать неколько компонентов
В гл. 9 мы реализуем повторно применимую фабрику класса, которая использует эту структуру.
Прежде чем идти дальше, я хотел бы подчеркнуть один важный момент. Даже если один и тот же код фабрики класса используется несколькими компонентами, данный экземпляр фабрики класса может создавать только компоненты, соответствующие одному CLSID. Соотношение между экземплярами фабрики класса и CLSID всегда будет один к одному. Хотя CFactory могла бы реализовать все наши классы, любой конкретный экземпляр CFactory может создавать только компоненты одного CLSID. Это следствие того, что IClassFactory::CreateInstance не передается в качестве параметра CLSID.
Выгрузка DLL
Я достаточно долго избегал разговора о LockServer и DllCanUnloadNow. Теперь пора заняться ими.
Как мы видели в гл. 5, когда клиент динамически связывается с компонентом, он должен загрузить DLL в память. В Win32 для этой цели используется функция LoadLibrary1. Когда мы закончили работу с DLL, хотелось бы выгрузить ее из памяти. Не стоит засорять память неиспользуемыми компонентами. Библиотека СОМ предоставляет функцию CoFreeUnusedLibraries, которая, как следует из названия, освобождает неиспользуемые библиотеки. Клиент должен периодически вызывать эту функцию в периоды бездействия.
Использование DllCanUnloadNow
Но как CoFreeUnusedLibraries определяет, какие из DLL больше не обслуживают ни одного клиента и могут быть выгружены? CoCreateUnusedLibraries опрашивает DLL, вызывая DllCanUnloadNow. DllCanUnloadNow сообщает СОМ, поддерживает ли данная DLL какие-либо объекты. Если DLL никого не обслуживает, CoFreeUnusedLibraries может ее выгрузить. Чтобы проверять, есть ли еще обслуживаемые компоненты, DLL поддерживает их счетчик. Для этого в CMPNT.CPP просто добавляется следующее объявление:
static long g_cComponents = 0;
Затем IClassFactory::CreateInstance или конструктор компонента увеличивают значение g_cComponents, а деструктор компонента уменьшает его. DllCanUnloadNow дает положительный ответ тогда, когда g_cComponents равна 0.
LockServer
Обратите внимание, что я подсчитываю только обслуживаемые DLL в данный момент компоненты, но не фабрики класса. Может оказаться, что логичнее было бы подсчитывать фабрики класса вместе с самими компонентами. Для серверов внутри процесса Вы, если хотите, можете подсчитывать их. Но в гл. 10 мы познакомимся с локальными серверами, которые реализуются в EXE, а не в DLL. С «внутренней» точки зрения, начало и конец работы у серверов внутри и вне процесса отличаются. (С точки зрения «внешней» клиент не видит никаких различий.) Сервер вне процесса запускается таким способом, что внутри него мы не можем подсчитывать фабрики класса, не попадая в порочный круг, в котором сервер никогда не освобождает себя. Более подробно это обсуждается в гл. 10. Достаточно сказать, что присутствие работающей фабрики класса не гарантирует, что сервер будет удерживаться в памяти.
Это создает трудную ситуацию. Выгрузка DLL, у которой имеются исполняющиеся фабрики класса, может вызвать проблемы у клиентов. Предположим, что у клиента имеется указатель на фабрику класса, а соответствующая DLL выгружена. Если клиент попытается использовать указатель на IClassFactory, произойдет сбой. Клиенту необходим некоторый способ удержания DLL в памяти, если он собирается использовать указатель IClassFactory за пределами одной функции. IClassFactory::LockServer позволяет клиенту удерживать
1 На самом деле библиотека COM использует CoLoadLibrary, которая обращается к LoadLibrary.