- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
97
Реализация фабрики класса
В этом разделе мы рассмотрим реализацию компонента, обращая особое внимание на реализацию фабрики класса. Но сначала посмотрим, как создаются сами фабрики класса.
Использование DllGetClassObject
В гл. 5 функция CallCreateInstance вызывала для создания компонента функцию CreateInstance из DLL. Функции CoGetClassObject также нужна точка входа DLL для создания фабрики класса компонента, которая (фабрика — ред.) реализована в одной DLL с компонентом. Эта точка входа называется DllGetClassObject. CoGetClassObject вызывает функцию DllGetClassObject, которая в действительности создает фабрику класса. DllGetClassObject объявлена так:
STDAPI DllGetClassObject( const CLSID& clsid, const IID& iid, void** ppv
);
Три параметра этой функции Вам уже знакомы: это те же параметры, что передаются CoGetClassObject. Первый
— идентификатор класса компонентов, которые будет создавать фабрика класса. Второй — идентификатор интерфейса фабрики, который желает использовать клиент. Указатель на этот интерфейс возвращается через третий параметр.
Весьма существенно, что DllGetClassObject передается CLSID. Этот параметр позволяет одной DLL поддерживать несколько компонентов, так как по значению CLSID можно выбрать подходящую фабрику класса.
Общая картина
Набросок общей картины создания компонента представлен на рис. 7-1. Здесь Вы увидите основных «участников» процесса. Во-первых, это клиент, который инициирует запрос обращением к CoGetClassObject. Вовторых, это библиотека СОМ, реализующая CoGetClassObject. В-третьих, это DLL. DLL содержит функцию
DllGetClassObject, которая вызывается CoGetClassObject. Задача DllGetClassObject — создать запрошенную фабрику класса. Способ, которым она это делает, оставлен полностью на усмотрение разработчика, так как он скрыт от клиента.
После того, как фабрика класса создана, клиент использует интерфейс IClassFactory для создания компонента. Как именно IClassFactory::CreateInstance создает компонент — дело разработчика. Как уже отмечалось, IClassFactory инкапсулирует этот процесс, поэтому при создании компонента фабрика класса может использовать специфические знания.
Клиент |
|
Библиотека COM |
|
DLL |
3 |
||
|
|
|
|||||
Вызывает |
1 |
CoGetClassObject |
2 |
DllGetClassObject |
Создает |
||
|
|
фабрику |
|||||
CoGetClassObject |
|
|
|
|
|
|
класса |
|
Возвращает |
|
|
|
|
|
|
|
IClassFactory |
|
4 |
|
|
|
|
|
5 |
Вызывает |
|
|
|
|
|
pIClassFactory |
IClassFactory::CreateInstance |
|
IClassFactory |
|
|||
|
|
|
|
|
|
||
|
|
7 |
Возвращает IX |
|
|
|
|
pIX |
|
|
8 |
Вызывает IX::Fx |
|
IX |
6 |
|
|
|
|
|
|||
|
|
|
|
|
Создает |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
компонент |
Рис. 7-1 Пронумерованный точки показывают последовательность создания клиентом компонента с помощью библиотеки СОМ и фабрики класса
Теперь мы готовы рассмотреть реализацию компонента.
98
Листинг кода компонента
Реализация компонента и его фабрики класса показаны в листинге 7-2. Фабрика реализована классом С++ CFactory. Первое, что Вы должны заметить в CFactory — это просто еще один компонент. Он реализует IUnknown так же, как и другие компоненты. Единственное отличие между реализациями CFactory и CA составляют наборы поддерживаемых интерфейсов.
Просматривая код, особое внимание уделите CFactory::CreateInstance и DllGetClassObject.
CMPNT.CPP
//
// Cmpnt.cpp
//
#include <iostream.h> #include <objbase.h>
#include "Iface.h" // Объявления интерфейсов #include "Registry.h" // Функции для работы с Реестром
// Функция трассировки
void trace(const char* msg) { cout << msg << endl; }
///////////////////////////////////////////////////////////
//
// Глобальные переменные
// |
// Описатель модуля DLL |
static HMODULE g_hModule = NULL; |
|
static long g_cComponents = 0; |
// Количество активных компонентов |
static long g_cServerLocks = 0; |
// Счетчик блокировок |
// Дружественное имя компонента
const char g_szFriendlyName[] = "Inside COM, Chapter 7 Example";
// Не зависящий от версии ProgID
const char g_szVerIndProgID[] = "InsideCOM.Chap07";
// ProgID
const char g_szProgID[] = "InsideCOM.Chap07.1";
///////////////////////////////////////////////////////////
//
// Компонент
//
class CA : public IX, public IY
{
public:
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
// Интерфейс IX
virtual void __stdcall Fx() { cout << "Fx" << endl; }
// Интерфейс IY
virtual void __stdcall Fy() { cout << "Fy" << endl; }
//Конструктор
CA();
//Деструктор
~CA();
private:
// Счетчик ссылок long m_cRef;
};
99
//
// Конструктор
//
CA::CA() : m_cRef(1)
{
InterlockedIncrement(&g_cComponents);
}
//
// Деструктор
//
CA::~CA()
{
InterlockedDecrement(&g_cComponents); trace("Компонент:\t\tСаморазрушение");
}
//
// Реализация IUnknown
//
HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown)
{
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IX)
{
*ppv = static_cast<IX*>(this); trace("Компонент:\t\tВернуть указатель на IX");
}
else if (iid == IID_IY)
{
*ppv = static_cast<IY*>(this); trace("Компонент:\t\tВернуть указатель на IY");
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK;
}
ULONG __stdcall CA::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CA::Release()
{
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this; return 0;
}
return m_cRef;
}
///////////////////////////////////////////////////////////
//
// Фабрика класса
//
class CFactory : public IClassFactory
{
public:
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
100
virtual ULONG |
__stdcall |
AddRef(); |
virtual ULONG |
__stdcall |
Release(); |
// Интерфейс IClassFactory |
|
|
virtual HRESULT __stdcall |
CreateInstance(IUnknown* pUnknownOuter, |
|
|
|
const IID& iid, |
|
|
void** ppv); |
virtual HRESULT __stdcall |
LockServer(BOOL bLock); |
|
// Конструктор |
|
|
CFactory() : m_cRef(1) {} |
|
// Деструктор
~CFactory() { trace("Фабрика класса:\t\tСаморазрушение"); }
private:
long m_cRef; };
//
// Реализация IUnknown для фабрики класса
//
HRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)
{
if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
{
*ppv = static_cast<IClassFactory*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK;
}
ULONG __stdcall CFactory::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG __stdcall CFactory::Release()
{
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this; return 0;
}
return m_cRef;
}
//
// Реализация IClassFactory
//
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid,
void** ppv)
{
trace("Фабрика класса:\t\tСоздать компонент");
//Агрегирование не поддерживается if (pUnknownOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
//Создать компонент
CA* pA = new CA; if (pA == NULL)
101
{
return E_OUTOFMEMORY;
}
// Вернуть запрошенный интерфейс
HRESULT hr = pA->QueryInterface(iid, ppv);
//Освободить указатель на IUnknown
//(При ошибке в QueryInterface компонент разрушит сам себя)
pA->Release(); return hr;
}
// LockServer
HRESULT __stdcall CFactory::LockServer(BOOL bLock)
{
if (bLock)
{
InterlockedIncrement(&g_cServerLocks);
}
else
{
InterlockedDecrement(&g_cServerLocks);
}
return S_OK;
}
///////////////////////////////////////////////////////////
//
// Экспортируемые функции
//
//
// Можно ли выгружать DLL?
//
STDAPI DllCanUnloadNow()
{
if ((g_cComponents == 0) && (g_cServerLocks == 0))
{
return S_OK;
}
else
{
return S_FALSE;
}
}
//
// Получить фабрику класса
//
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
{
trace("DllGetClassObject:\tСоздать фабрику класса");
//Можно ли создать такой компонент? if (clsid != CLSID_Component1)
{
return CLASS_E_CLASSNOTAVAILABLE;
}
//Создать фабрику класса
CFactory* pFactory = new CFactory; |
// Счетчик ссылок устанавливается |
if (pFactory == NULL) |
// в конструкторе в 1 |
|
|
{ |
|
return E_OUTOFMEMORY; |
|
} |
|
102
// Получить требуемый интерфейс
HRESULT hr = pFactory->QueryInterface(iid, ppv); pFactory->Release();
return hr;
}
//
// Регистрация сервера
//
STDAPI DllRegisterServer()
{
return RegisterServer(g_hModule, CLSID_Component1, g_szFriendlyName, g_szVerIndProgID, g_szProgID);
}
//
// Удаление сервера из Реестра
//
STDAPI DllUnregisterServer()
{
return UnregisterServer(CLSID_Component1, g_szVerIndProgID, g_szProgID);
}
///////////////////////////////////////////////////////////
//
// Реализация модуля DLL
//
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
g_hModule = hModule;
}
return TRUE;
}
Листинг 7-2 Полный код компонента, фабрики класса и функций, экспортируемых из DLL
Клиент: |
Вызвать CoCreateInstance для |
Клиент: |
создания компонента и получения интерфейса IX |
DllGetClassObject: |
Создать фабрику класса |
Фабрика класса: |
Создать компонент |
Компонент: |
Вернуть указатель на IX |
Фабрика класса: |
Саморазрушение |
Клиент: |
IX получен успешно |
Fx |
Запросить интерфейс IX |
Клиент: |
|
Компонент: |
Вернуть указатель на IY |
Клиент: |
IY получен успешно |
Fy |
Освободить интерфейс IY |
Клиент: |
|
Клиент: |
Запросить интерфейс IZ |
Клиент: |
Не могу получить интерфейс IZ |
Клиент: |
Освободить интерфейс IX |
Компонент: |
Саморазрушение |
Только что представленная реализация DllGetClassObject делает три вещи. Во-первых, она проверяет, соответствует ли запрос именно той фабрике, которую она умеет создавать. Затем при помощи операции new создается фабрика класса. Наконец, DllGetClassObject запрашивает у фабрики класса интерфейс, требуемый клиенту. Реализация IClassFactory::CreateInstance похожа на реализацию DllGetClassObject. Обе функции создают компонент и запрашивают у него интерфейс. IClassFactory::CreateInstance создает CA, тогда как
DllGetClassObject — CFactory.