- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
7 глава
Фабрика класса
Когда я был совсем маленьким и еще не собирался стать пожарным, я мечтал стать дизайнером наборов конструктора Lego. У меня были самые разные идеи относительно хитроумных новых деталей, из которых можно было бы строить потрясающие модели. Я даже послал несколько проектов в компанию (которая не стала запускать их в производство). Тем не менее, несмотря на отсутствие у фирмы интереса к моим новациям, сейчас я мог бы производить детали Lego прямо у себя в спальне.
Уже появились машинки, которые называют трехмерными принтерами (3D-printers), — и это название очень им подходит. Они похожи на струйные принтеры, но выбрасывают тонкую струю пластика под давлением, а не чернила. Такой принтер наносит пластмассу слоями тоньше миллиметра. Повторная «печать» по одному и тому же месту позволяет создать сложные трехмерные объекты. Их можно использовать как прототипы или формы для изготовления деталей, а иногда и как готовые детали. С такой машинкой можно было бы организовать Домашнюю Фабрику Пластиковых Деталей. При помощи пакета САПР можно было бы в мгновение ока проектировать и производить новые детали. На такой домашней фабрике Вы могли бы сделать ту хитрую детальку с переходом 1x3, без которой никак не собиралась вся модель. Вообще у Вас больше не было бы недостатка в деталях — хотя компания Lego, вероятно, предпочла бы все же продавать Вам свои.
В этой главе я собираюсь рассмотреть своего рода фабрику, на которой производятся не детали Lego, а компоненты. Эта фабрика класса — просто компонент с интерфейсом для создания других компонентов, так что обойдется она нам дешевле, чем трехмерный принтер за 50000 долларов.
Но прежде чем заняться фабрикой класса, мы познакомимся с самым простым способом создания компонентов
— при помощи функции CoCreateInstance. Не удивительно, что этим способом пользуются чаще всего. К сожалению, он недостаточно гибок и годится не для всех компонентов. Все компоненты создаются на этой фабрике — CoCreateInstance при создании компонента тоже пользуется ее услугами, но неявно и незаметно для вызывающей программы. Клиент получает большую свободу в создании компонентов, если он прямо использует фабрику. Точно так же, как у Вас было бы больше возможностей, если бы Вы не покупали детали Lego у фирмы, а делали их сами, на Домашней Фабрике Пластиковых Деталей.
CoCreateInstance
Для создания компонентов в библиотеке СОМ служит функция CoCreateInstance, которая, получив CLSID, создает экземпляр соответствующего компонента и возвращает интерфейс этого экземпляра. В этом разделе мы рассмотрим использование CoCreateInstance и увидим, с какими ограничениями оно связано. Но сначала давайте посмотрим на саму функцию.
Прототип CoCreateInstance
Объявление CoCreateInstance приведено ниже:
HRESULT __stdcall CoCreateInstance( |
|
const CLSID& clsid, |
// Внешний компонент |
IUnknown* pUnknownOuter, |
|
DWORD dwClsContext, |
// Контекст сервера |
const IID& iid, |
|
void** ppv |
|
); |
|
У функции четыре входных параметра (in) и единственный выходной (out). Первый параметр — CLSID создаваемого компонента. Второй параметр используется для агрегирования компонентов и будет обсуждаться в следующей главе. Третий параметр — dwClsContext — ограничивает контекст исполнения компонента, с которым данный клиент может работать. Этот параметр мы рассмотрим позже.
92
Четвертый параметр, iid — это IID интерфейса, который мы хотим использовать для работы с компонентом. Указатель на этот интерфейс возвращается через последний параметр — ppv. Поскольку в CoCreateInstance передается IID, клиент может не вызывать QueryInterface для созданного компонента.
Использование CoCreateInstance
Используется CoCreateInstance так же просто, как и QueryInterface:
// Создать компонент
IX* pIX = NULL;
HRESULT hr = ::CoCreateInstance( CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX,
(void**)&pIX
);
if (SUCCEEDED(hr))
{
pIX->Fx(); pIX->Release();
};
В данном примере мы создаем компонент, задаваемый CLSID_Component1. Мы не агрегируем его, поэтому значением второго параметра является NULL. В следующей главе мы будем передавать функции значение, отличное от NULL. Параметр CLSCTX_INPROC_SERVER заставляет CoCreateInstance загружать только те компоненты, которые содержатся в серверах в процесса или в DLL.
Значения, передаваемые CoCreateInstance в качестве двух последних параметров, — те же самые, которые мы передавали бы QueryInterface. В данном примере мы передаем IID_IX, чтобы запросить интерфейс IX, который возвращается указателем pIX. Если вызов CoCreateInstance был успешным, то интерфейс IX готов к работе. Освобождение же интерфейса IX указывает, что клиент завершил использование и этого интерфейса, и самого компонента.
Контекст класса
Третий параметр CoCreateInstance — dwClsContext — используется для управления тем, где может исполняться компонент: в том же процессе, что и клиент, в другом процессе или на другой машине. Значением параметра может быть комбинация признаков, приведенных ниже:
CLSCTX_INPROC_SERVER |
Клиент принимает только компоненты, которые исполняются в одном с ним |
|
процессе. Подобные компоненты должны быть реализованы в DLL. |
CLSCTX_INPROC_HANDLER |
Клиент будет работать с обработчиками в процессе. Обработчик в процессе |
|
— это компонент внутри процесса, который реализует только часть |
|
компонента. Другие части реализуются компонентом вне процесса — |
|
локальным или удаленным сервером. |
CLSCTX_LOCAL_SERVER |
Клиент будет работать с компонентами, которые выполняются в другом |
|
процесса, но на той же самой машине. Локальные серверы реализуются в |
|
EXE, как мы увидим в гл. 10. |
CLSCTX_REMOTE_SERVER |
Клиент допускает компоненты, выполняющиеся на другой машине. |
|
Использование этого флага требует задействования DCOM. Мы рассмотрим |
|
его в гл. 10. |
Один и тот же компонент может быть доступен во всех трех контекстах: удаленном, локальном и в процессе. В некоторых случаях клиент может пожелать использовать только компоненты в процессе, так как они работают быстрее. В других случаях он может не захотеть использовать компоненты, работающие в его собственном процессе, так как они имеют доступ к любому участку памяти процесса, что не слишком безопасно. Однако в большинстве случаев клиента не интересует контекст, в котором исполняется компонент. Поэтому в OBJBASE.H определены удобные константы, которые комбинируют (с помощью побитового ИЛИ) приведенные выше значения (см. табл. 7-1).
Значение CLSCTX_REMOTE_SERVER добавляется к CLSCTX_ALL и CLSCTX_SERVER, только если перед включением OBJBASE.H Вы определили символ препроцессора _WIN32_WINNT большим или равным 0x0400. (Тот же эффект даст определение перед включением OBJBASE.H символа препроцессора _WIN32_DCOM.) Предупреждение: если Вы передадите функции CoCreateInstance значение CLSCTX_REMOTE_SERVER на системе, которая не поддерживает DCOM, то CoCreateInstance возвратит ошибку E_INVALIDARG. Это легко
93
может случиться, если Вы компилируете свою программу с _WIN32_WINNT, большим или равным 0x0400, и затем запускаете ее в системе Microsoft Windows NT 3.51 или Microsoft Windows 95, которые не поддерживают
DCOM. Более подробно CLSCTX_LOCAL_SERVER и CLSCTX_REMOTE_SERVER рассматриваются в гл. 10.
Таблица 7-1 Предопределенные комбинации признаков контекста исполнения
Константы |
Значения |
CLSCTX_INPROC |
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
CLSCTX_ALL |
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | |
|
CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER |
CLSCTX_SERVER |
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | |
|
CLSCTX_REMOTE_SERVER |
Листинг кода клиента
В качестве примера в этой главе мы создадим наши первые настоящие клиент и компонент СОМ. Копии всех исходных файлов находятся на прилагающемся диске. Листинг 7-1 содержит код клиента. Единственным его существенным отличием от клиентов в гл. 5 является создание компонента с помощью CoCreateInstance. Среди других особенностей можно назвать использование CoInitialize и CoUninitialize для инициализации библиотеки СОМ (как обсуждается в гл. 6).
CLIENT.CPP
//
// Client.cpp – реализация клиента
//
#include <iostream.h> #include <objbase.h>
#include "Iface.h"
void trace(const char* msg) { cout << "Клиент: \t\t" << msg << endl; }
//
// функция main
//
int main()
{
// Инициализация библиотеки COM CoInitialize(NULL);
trace("Вызвать CoCreateInstance для создания"); trace(" компонента и получения интерфейса IX");
IX* pIX = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIX);
if (SUCCEEDED(hr))
{
trace("IX получен успешно");
pIX->Fx(); // Использовать интерфейс IX
trace("Запросить интерфейс IY"); IY* pIY = NULL;
hr = pIX->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr))
{
trace("IY получен успешно");
pIY->Fy(); // Использовать интерфейса IY
pIY->Release();
trace("Освободить интерфейс IY");
}
else
{
trace("Не могу получить интерфейс IY");
}