- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
152
if (pData->IsClassID(clsid))
{
//Идентификатор класса найден в массиве компонентов,
//которые мы можем создать. Поэтому создадим фабрику
//класса для данного компонента. Чтобы задать фабрике
//класса тип компонентов, которые она должна создавать,
//ей передается структура CFactoryData
*ppv = (IUnknown*) new CFactory(pData); if (*ppv == NULL)
{
return E_OUTOFMEMORY;
}
return NOERROR;
}
}
return CLASS_E_CLASSNOTAVAILABLE;
}
Листинг 9-5 Реализация CFactory::GetClassObject
Реализация CreateInstance в CFACTORY.CPP
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid,
void** ppv)
{
// При агрегировании IID должен быть IID_IUnknown
if ((pUnknownOuter != NULL) && (iid != IID_IUnknown))
{
return CLASS_E_NOAGGREGATION;
}
//Создать компонент
CUnknown* pNewComponent;
HRESULT hr = m_pFactoryData->CreateInstance(pUnknownOuter, &pNewComponent); if (FAILED(hr))
{
return hr;
}
//Initialize the component.
hr = pNewComponent->Init(); if (FAILED(hr))
{
// Ошибка инициализации. Удалить компонент pNewComponent->NondelegatingRelease(); return hr;
}
// Получить запрошенный интерфейс
hr = pNewComponent->NondelegatingQueryInterface(iid, ppv);
// Освободить ссылку, удерживаемую фабрикой класса pNewComponent->NondelegatingRelease();
return hr;
}
Листинг 9-6 Реализация CFactory::CreateInstance
Вот и все тонкости создания компонентов с помощью CFactory. Реализуйте компонент и поместите его данные в структуру — это все!
Использование CUnknown и CFactory
Я очень рад, что теперь мы сможем повторно использовать реализацию интерфейс IUnknown и фабрики класса. Вам наверняка уже надоел один и тот же код QueryInterface, AddRef и Release. Я тоже устал от него. Отныне наши компоненты не будут реализовывать AddRef и Release, а будут лишь добавлять поддержку нужных интерфейсов в QueryInterface. Мы также сможем использовать простую функцию создания, а не писать заново целую фабрику класса. Наши новые клиенты будут похожи на клиент, представленный в листинге 9-7.
153
CMPNT2.H
//
// Cmpnt2.h - Компонент 2
//
#include "Iface.h"
#include "CUnknown.h"// Базовый класс для IUnknown
///////////////////////////////////////////////////////////
//
// Компонент B
//
class CB : public CUnknown, public IY
{
public:
// Создание
static HRESULT CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent);
private:
//Объявление делегирующего IUnknown DECLARE_IUNKNOWN
//Неделегирующий IUnknown
virtual HRESULT __stdcall
NondelegatingQueryInterface(const IID& iid, void** ppv);
// Интерфейс IY
virtual void __stdcall Fy();
//Инициализация virtual HRESULT Init();
//Очистка
virtual void FinalRelease();
// Конструктор
CB(IUnknown* pUnknownOuter);
//Деструктор
~CB();
//Указатель на внутренний агрегируемый объект
IUnknown* m_pUnknownInner;
//Указатель на интерфейс IZ, поддерживаемый внутренним компонентом
IZ* m_pIZ;
};
Листинг 9-7 Компонент, использующий IUnknown, реализованный в CUnknown
В листинге 9-7 представлен заголовочный файл для Компонента 2 из примера этой главы. Код мы рассмотрим чуть позже. В этом примере Компонент 1 реализует интерфейс IX самостоятельно. Для того, чтобы предоставить интерфейсы IY и IZ, он агрегирует Компонент 2. Компонент 2 реализует IY и агрегирует Компонент 3, который, в свою очередь, реализует IZ. Таким образом, Компонент 2 — одновременно и агрегируемый, и агрегирующий.
Посмотрим листинг 9-7 от начала до конца. Я отмечу все интересные моменты, а затем мы рассмотрим их подробно.
Компонент наследует CUnknown, который предоставляет реализацию IUnknown. Мы объявляем статическую функцию, которую CFactory будет использовать для создания компонента. Имя этой функции для CFactory не имеет значения, поэтому можно назвать ее как угодно.
Далее мы реализуем делегирующий IUnknown с помощью макроса DECLARE_IUNKNOWN. DECLARE_IUNKNOWN реализует делегирующий IUnknown, а CUnknown — неделегирующий.
Хотя CUnknown полностью реализует AddRef и Release, он не может предоставить полной реализации QueryInterface, так как ему неизвестно, какие интерфейсы поддерживает наш компонент. Поэтому компонент реализует NondelegatingQueryInterface для обработки запросов на его собственные интерфейсы.
154
Производные классы переопределяют Init для создания внутренних компонентов при агрегировании или включении. CUnknown::NondelegatingRelease вызывает FinalRelease непосредственно перед тем, как удалить объект. Последнюю переопределяют те компоненты, которым необходимо освободить указатели на внутренние компоненты. CUnknown::FinalRelease увеличивает счетчик ссылок, чтобы предотвратить рекурсивную ликвидацию компонента.
Теперь рассмотрим различные аспекты Компонента 2, код которого представлен в листинге 9-8.
CMPNT2.CPP
//
// Cmpnt2.cpp - Компонент 2
//
#include <objbase.h>
#include "Iface.h" #include "Util.h"
#include "CUnknown.h"// Базовый класс для IUnknown #include "Cmpnt2.h"
static inline void trace(char* msg)
{ Util::Trace("Компонент 2", msg, S_OK); }
static inline void trace(char* msg, HRESULT hr) { Util::Trace("Компонент 2", msg, hr); }
///////////////////////////////////////////////////////////
//
// Реализация интерфейса IY
//
void __stdcall CB::Fy()
{
trace("Fy");
}
//
// Конструктор
//
CB::CB(IUnknown* pUnknownOuter)
: CUnknown(pUnknownOuter), m_pUnknownInner(NULL), m_pIZ(NULL)
{
// Пустой
}
//
// Деструктор
//
CB::~CB()
{
trace("Самоликвидация");
}
//
// Реализация NondelegatingQueryInterface
//
HRESULT __stdcall CB::NondelegatingQueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IY)
{
return FinishQI(static_cast<IY*>(this), ppv);
}
else if (iid == IID_IZ)
{
return m_pUnknownInner->QueryInterface(iid, ppv);
}
else
{
return CUnknown::NondelegatingQueryInterface(iid, ppv);
}
}
155
//
// Инициализировать компонент и создать внутренний компонент
//
HRESULT CB::Init()
{
trace("Создание агрегируемого Компонента 3");
HRESULT hr = CoCreateInstance(CLSID_Component3, GetOuterUnknown(),
CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnknownInner);
if (FAILED(hr))
{
trace("Не могу создать внутренний компонент", hr); return E_FAIL;
}
trace("Получить указатель на интерфейс IZ для использования в дальнейшем"); hr = m_pUnknownInner->QueryInterface(IID_IZ, (void**)&m_pIZ);
if (FAILED(hr))
{
trace("Внутренний компонент не поддерживает IZ", hr); m_pUnknownInner->Release();
m_pUnknownInner = NULL; return E_FAIL;
}
// Компенсировать увеличение счетчика ссылок из-за вызова QI trace("Указатель на интерфейс IZ получен. Освободить ссылку.");
GetOuterUnknown()->Release();
return S_OK;
}
//
// FinalRelease – Вызывается из Release перед удаление компонента
//
void CB::FinalRelease()
{
//Вызов базового класса для увеличения m_cRef и предотвращения рекурсии
CUnknown::FinalRelease();
//Учесть GetOuterUnknown()->Release в методе Init
GetOuterUnknown()->AddRef();
//Корректно освободить указатель, так как подсчет ссылок
//может вестись поинтерфейсно
m_pIZ->Release();
//Освободить внутренний компонент
//(Теперь мы можем это сделать, так как освободили его интерфейсы)
if (m_pUnknownInner != NULL)
{
m_pUnknownInner->Release();
}
}
///////////////////////////////////////////////////////////
//
// Функция создания для CFactory
//
HRESULT CB::CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent)
{
*ppNewComponent = new CB(pUnknownOuter); return S_OK;
}
Листинг 9-8 Реализация компонента, использующего CUnknown и CFactory
156
NondelegatingQueryInterface
Вероятно, самая интересная часть компонента — NondelegatingQueryInterface. Мы реализуем ее почти так же, как QueryInterface в предыдущих главах. Обратите, однако, внимание на два отличия. Во-первых, мы используем функцию FinishQI, причем делаем это лишь для удобства; мы не обязаны ее использовать. FinishQI лишь несколько облегчает реализацию NondeletgatingQueryInterface в производных классах. Код этой функции показан ниже:
HRESULT CUnknown::FinishQI(IUnknown* pI, void** ppv)
{
*ppv = pI; pI->AddRef(); return S_OK;
}
Второе отличие в том, что нам нет необходимости обрабатывать запрос на IUnknown. Базовый класс выполняет обработку для IUnknown и всех интерфейсов, о которых мы не знаем:
HRESULT __stdcall CUnknown::NondelegatingQueryInterface(const IID& iid, void** ppv)
{
// CUnknown поддерживает только IUnknown if (iid == IID_IUnknown)
{
return FinishQI(reinterpret_cast<IUnknown*> (static_cast<INondelegatingUnknown*>(this)), ppv);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
}
Все вместе, шаг за шагом
Приведенный выше код показывает, как легко писать компоненты с помощью CUnknown и CFactory. Давайте рассмотрим всю процедуру целиком. Далее приводится последовательность шагов создания компонента, его фабрики класса и DLL, в которой он будет находиться:
1. Напишите класс, реализующий компонент.
!" Базовым классом компонента должен быть либо CUnknown, либо другой класс, производный от него. !" При помощи макроса DECLARE_IUNKNOWN реализуйте делегирующий IUnknown.
!" Инициализируйте CUnknown в конструкторе своего класса.
!" Реализуйте NondelegatingQueryInterface, добавив интерфейсы, которые поддерживает Ваш класс, но не поддерживает базовый класс. Вызовите базовый класс для тех интерфейсов, которые не обрабатываются в Вашем классе.
!" Если Ваш компонент требует дополнительной инициализации после конструктора, реализуйте функцию Init. Создайте включаемые и агрегируемые компоненты, если это необходимо.
!" Если после освобождения, но перед удалением компонент должен выполнить какую-либо очистку, реализуйте FinalRelease. Освободите указатели на включаемые и агрегируемые компоненты.
!" Реализуйте для своего компонента статическую функцию CreateInstance. !" Реализуйте поддерживаемые компонентом интерфейсы.
2.Повторите шаг 1 для каждого из компонентов, которые Вы хотите поместить в данную DLL.
3.Напишите фабрику класса.
!" Создайте файл для определения глобального массива CfactoryData — g_FactoryDataArray.
!" Определите g_FactoryDataArray и поместите в него информацию о всех компонентах, обслуживаемых этой DLL.
!" Определите g_cFactoryDataEntries, которая должна содержать число компонентов в массиве g_FactoryDataArray.
4.Создайте DEF файл с описанием точек входа в DLL.