- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
87
Реализация категорий компонентов
Самое приятное в категориях компонентов то, что для их использования Вам не нужно возиться с Реестром самостоятельно. В системах Windows имеется стандартный Диспетчер категорий компонентов (Component Category Manager), который проделает за Вас всю работу. Этот Диспетчер (CLSID_StdComponentCategoryMgr) —
стандартный компонент СОМ, реализующий два интерфейса, ICatRegister и ICatInformation. ICatRegister используется для регистрации и удаления категорий. Он также может использоваться для добавления и удаления компонентов к категории. ICatInformation применяется для получения информации о категориях в системе. С помощью этого интерфейса Вы можете найти:
!" все категории, зарегистрированные в системе; !" все компоненты, принадлежащие данной категории;
!" все категории, к которым принадлежит данный компонент.
Более полная документация Диспетчера категорий компонентов содержится на прилагающемся к книге диске.
Поищите ICatRegister и ICatInformation в файле ACTIVEX.MVB.
С помощью Диспетчера категорий легко добавлять и удалять категории. Использование Диспетчера показано в примере программы из этой главы, который можно найти на диске. Программа выдает список зарегистрированных в системе категорий компонентов, добавляет новую категорию и, наконец, удаляет эту категорию. Если у Вас эта программа не работает, то возможно, что на Вашем компьютере не установлены некоторые файлы. В прилагаемом к примеру файле README указаны файлы, которые могут отсутствовать, и поясняется, как их установить.
Даже если Вам не нужны категории компонентов, данный пример все равно представляет интерес, так как это первый случай использования нами компонента СОМ, реализованного кем-то другим.
OleView
Редактор Реестра показывает Реестр «в чистом виде», что полезно для изучения. Вы должны знать организацию Реестра, чтобы реализовать самостоятельно регистрирующиеся компоненты или клиентов, которые будут опрашивать Реестр. Однако если Вам нужна дополнительная информация об установленных на компьютере компонентах, использование Редактора Реестра может потребовать слишком много времени. Ведь он, по существу, показывает все данные в виде списка CLSID.
Другая программа из Win32 SDK — OleView — представляет информацию на более высоком уровне. Вместо длинного списка CLSID и других GUID OleView отображает деревья, содержащие элементы с дружественными именами. Кроме того, OleView позволяет просматривать категории компонентов, установленных в системе. Для изучения лучше всего запустить OleView и поработать. Я использовал эту программу для проверки моего кода саморегистрации. Если OleView может найти информацию, то, скорее всего, эта информация помещена в правильное место.
Некоторые функции библиотеки COM
Всем клиентам и компонентам СОМ приходится выполнять много типовых операций. Чтобы сделать выполнение этих операций стандартным и совместимым, СОМ предоставляет библиотеку функций. Библиотека реализована в OLE32.DLL. Для статической компоновки с ней Вы можете использовать OLE32.LIB. В этом разделе мы рассмотрим некоторые из важных типовых операций.
Инициализация библиотеки COM
Во-первых, рассмотрим инициализацию самой библиотеки СОМ. Процесс должен вызвать CoInitialize для инициализации библиотеки, прежде чем использовать ее функции (за исключением функции CoBuildVersion, возвращающей номер версии библиотеки). Когда процесс завершает работу с библиотекой СОМ, он должен вызвать CoUninitialize. Прототипы этих функций приведены ниже:
HRESULT CoInitialize(void* reserved); |
// Значение параметра должно быть NULL |
void CoUninitialize(); |
|
Библиотека СОМ требует инициализации только один раз для каждого процесса. Много кратные вызовы процессом CoInitialize допустимы, но каждому из них должен соответствовать отдельный вызов CoUninitialize. Если CoInitilialize уже была вызвана данным процессом, то она возвращает не S_OK, а S_FALSE.
Поскольку в данном процессе библиотеку СОМ достаточно инициализировать лишь один раз, и поскольку эта библиотека используется для создания компонентов, компонентам в процессе не требуется инициализировать библиотеку. По общему соглашению СОМ инициализируется в EXE, а не в DLL.
88
Использование OleInitialize
OLE, построенная «поверх» СОМ, добавляет поддержку библиотек типов, буфера обмена, перетаскивания мышью, документов ActiveX, Автоматизации и управляющих элементов ActiveX. Библиотека OLE содержит дополнительную поддержку этих возможностей. Если Вы хотите использовать все это, следует вызывать
OleInitialize и OleUninitialize вместо CoInitialize и CoUninitialize. Обычно проще всего вызвать функции Ole* и
забыть о них. Функции Ole* вызывают соответствующие функции Com*. Однако использование Ole* вместо Com* приводит к излишним расходам ресурсов и времени, если расширенные возможности не используются.
CoInitializeEx
В операционных системах Windows, поддерживающих DCOM, Вы можете использовать CoInitializeEx, чтобы пометить компонент как использующий модель свободных потоков (free-threaded). Более подробная информация о CoInitializeEx содержится в гл. 12.
Управление памятью
Очень часто внутренняя функция компонента выделяет блок памяти, который возвращается клиенту через выходной параметр. Но кто и каким образом будет эту память освобождать? Наибольшая проблема связана с тем, кто освободит память, — ведь клиент и компонент могут быть реализованы разными людьми, написаны на разных языках и даже выполняться в разных процессах. Необходим стандартный способ выделения и освобождения такой памяти.
Решение предоставляет менеджер памяти задачи (task memory allocator) СОМ. С его помощью компонент может передать клиенту блок памяти, который тот будет в состоянии освободить. Кроме того, менеджер «гладко» работает с потоками, поэтому его можно применять в многопоточных приложениях.
Как обычно, менеджер используется через интерфейс. В данном случае интерфейс называется Imalloc и возвращается функцией CoGetMalloc. IMalloc::Alloc выделяет блок памяти, а IMalloc::Free освобождает память, выделенную с помощью IMalloc::Alloc. Однако обычно вызывать CoGetMalloc для получения указателя на интерфейс, вызывать с помощью этого указателя функцию и затем освобождать указатель — значит делать слишком много работы. Поэтому библиотека СОМ предоставляет удобные вспомогательные функции —
CoTaskMemAlloc и CoTaskMemFree:
void* CoTaskMemAlloc(
ULONG cb // Размер выделяемого блока в байтах
};
void CoTaskMemFree(
void* pv // Указатель на освобождаемый блок памяти
};
Память, выделенную и переданную при помощи выходного параметра, всегда освобождает вызывающая процедура (пользующаяся CoTaskMemFree).
Преобразование строк в GUID
В Реестре содержатся строковые представления CLSID. Поэтому нам нужны специальные функции для преобразования CLSID в строку и обратно. В библиотеке СОМ имеется несколько удобных функций такого рода. StringFromGUID2 конвертирует GUID в строку:
wchar_t szCLSID[39];
int r = ::StringFromGRUID2(CLSID_Component1, szCLSID, 39);
StringFromGUID2 генерирует строку символов Unicode, т.е. строку двухбайтовых символов типа wchar_t, а не char. В системах, не использующих Unicode, Вам придется преобразовать результат в char. Для этого можно прибегнуть к функции ANSI wcstombs, как показано ниже.
#ifndef _UNICODE
// Преобразование из строки Unicode в обычную char szCLSID_single[39]; wcstombs(szCLSID_single, szCLSID, 39);
#endif
Есть еще несколько функций, выполняющих аналогичные операции:
Функция Назначение
StringFromCLSID |
Безопасное с точки зрения приведения типов преобразование CLSID |
|
в строку |