- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
117
Клиенты агрегируемого компонента никогда не получают указатели на неделегирующий IUnknown внутреннего компонента. Всякий раз, когда клиент запрашивает указатель на IUnknown, он получает IUnknown внешнего компонента. Указатель на неделегирующий IUnknown внутреннего компонента передается только внешнему компоненту. Теперь рассмотрим, как реализовать делегирующий IUnknown.
Реализация делегирующего IUnknown
К счастью, реализация делегирующего IUnknown проста — она передает вызовы либо внешнему, либо неделегирующему IUnknown. Далее приводится объявление компонента, поддерживающего агрегирование. Компонент содержит указатель m_pUnknownOuter. Если компонент агрегирован, указатель ссылается на внешний IUnknown. Если компонент не агрегирован, этот указатель ссылается на неделегирующий IUnknown. Всякий раз при обращении к делегирующему IUnknown вызов переадресуется интерфейсу, на который указывает m_pUnknownOuter. Делегирующий IUnknown реализован функциями, подставляемыми в строку (inline):
class CB : public IY, INondelegatingUnknown
{
public:
// Делегирующий IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
{
// Делегировать QueryInterface
return m_pUnknownOuter->QueryInterface(iid, ppv);
}
virtual ULONG __stdcall AddRef()
{
// Делегировать AddRef
return m_pUnknownOuter->AddRef();
}
virtual ULONG __stdcall Release()
{
// Делегировать Release
return m_pUnknownOuter->Release();
}
//Неделегирующий IUnknown virtual HRESULT __stdcall
NondelegatingQueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall NondelegatingAddRef();
virtual ULONG __stdcall NondelegatingRelease();
//Интерфейс IY
virtual void Fy() { cout << “Fy” << endl; }
// Конструктор
CB(IUnknown* pUnknownOuter);
// Деструктор
~CB();
private:
long m_cRef;
IUnknown* m_pUnknownOuter;
};
Создание внутреннего компонента
Теперь, когда мы знаем, как реализовать внутренний компонент, обсудим, как он создается внешним компонентом. Чтобы пройти весь процесс создания, от начала до конца, рассмотрим код трех функций: функция Init внешнего компонента начинает процесс; затем вступает функция фабрики класса CreateInstance и конструктор внутреннего компонента.
Функция Init внешнего компонента
Первое, что делает внешний компонент при агрегировании — создает внутренний компонент. Основное различие между включением и агрегированием состоит в том, что во втором случае внешний компонент передает внутреннему внешний IUnknown. Приведенный ниже фрагмент кода показывает, как внешний компонент создает
118
внутренний. Обратите внимание, что второй параметр CoCreateInstance — это указатель на интерфейс IUnknown внешнего компонента.
Кроме того, отметьте себе, что пятый параметр запрашивает у внутреннего компонента указатель на IUnknown. Фабрика класса будет возвращать указатель на неделегирующий IUnknown внутреннего компонента. Как мы уже видели, это указатель необходим внешнему компоненту для передачи вызовов QueryInstance внутреннему компоненту. Здесь внешний компонент обязан запрашивать указатель на IUnknown; в противном случае он никогда не сможет получить его. Внешний компонент должен сохранить неделегирующий IUnknown внутреннего компонента для последующего использования.
В данном примере нет необходимости явно приводить указатель this к указателю на IUnknown, так как CA наследует только IX, и, следовательно, неявное преобразование не будет неоднозначным.
HRESULT CA::Init()
{
IUnknown* pUnknownOuter = this;
HRESULT hr = CoCreateInstance(CLSID_Component2, pUnknownOuter, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnknownOuter);
if (FAILED(hr))
{
return E_FAIL;
}
return S_OK;
}
Реализация IClassFactory::CreateInstance внешнего компонента вызывает CA::Init. В других отношениях реализация IClassFactory внешнего компонента остается неизменной. Фабрика же класса внутреннего компонента подверглась некоторым изменениям, которые мы теперь и рассмотрим.
Функция IClassFactory::CreateInstance внутреннего компонента
Реализация IClassFactory::CreateInstance внутреннего компонента изменена, чтобы использовать InondelegatingUnknown вместо IUnknown. Код этой функции приведен ниже, отличия от предыдущих вариантов CreateInstance не возвращает автоматически ошибку, если pUnknownOuter не равен NULL (т.е. когда внешний компонент желает агрегировать внутренний). Однако CreateInstance обязана возвратить ошибку, если при этом iid отличен от IID_IUnknown. Когда компонент создается как внутренний в агрегате, он может возвратить только интерфейс IUnknown, иначе внешний компонент никогда не получил бы неделегирующего IUnknown (поскольку вызовы QueryInterface будут делегированы внешнему IUnknown).
HRESULT __stdcall Cfactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid,
void** ppv)
{
// При агрегировании iid должен быть IID_IUnknown
if ((pUnknownOuter != NULL) && (iid != IID_IUnknown))
{
return CLASS_E_NOAGGREGATION;
}
// Создать компонент
CB* pB = new CB(pUnknownOuter); if (pB == NULL)
{
return E_OUTOFMEMORY;
}
// Получить запрошенный интерфейс
HRESULT hr = pB->NondelegatingQueryInterface(iid, ppv); PB->NondelegatingRelease();
return hr;
}
Для получения запрашиваемого интерфейса вновь созданного внутреннего компонента показанная выше функция CreateInstance вызывает не QueryInterface, а NondelegatingQueryInterface. Если внутренний компонент агрегируется, вызовы QueryInterface он будет делегировать внешнему IUnknown. Фабрика класса должна возвратить указатель на неделегирующий QueryInterface, поэтому он вызывает NondelegatingQueryInterface.
119
Конструктор внутреннего компонента
В приведенном выше коде CreateInstance указатель на внешний IUnknown передается конструктору внутреннего компонента. Конструктор инициализирует m_pUnknownOuter, которая используется делегирующим IUnknown для передачи вызовов либо неделегирующему, либо внешнему IUnknown. Если компонент не агрегируется (pUnknownOuter есть NULL), конструктор помещает в m_pUnknownOuter указатель на неделегирующий IUnknown. Это показано в приведенном ниже фрагменте:
CB::CB(IUnknown* pUnknownOuter) : m_cRef(1)
{
::InterlockedIncrement)&g_cComponents;
if (pUnknownOuter == NULL)
{
// Не агрегируется: использовать неделегирующий IUnknown m_pUnknownOuter = reinterpret_cast<IUnknown*>(
static_cast<INondelegatingUnknown*>(this)
);
}
else
{
// Агрегируется: использовать внешний IUnknown m_pUnknownOuter = pUnknownOuter;
}
}
Указатели внешнего компонента на интерфейсы внутреннего компонента
Когда я реализовывал CA::Init при создании внутреннего компонента, я запрашивал интерфейс IUnknown, а не IY. Однако наш компонент в действительности агрегирует IY. Поэтому неплохо было бы в самом начале проверить, поддерживается ли интерфейс IY внутренний компонент. Но, как указывалось выше, при агрегировании компонента внешний компонент может запрашивать только интерфейс IUnknown. Cfactory::CreateInstance возвращает CLASS_E_NOAGGREGATION, если ей передано что-либо, отличное от IID_IUnknown. Следовательно, нам необходимо запросить у внутреннего компонента интерфейс IY после его (компонента) создания.
Но здесь необходимо быть аккуратным и не запутаться. Когда Вы вызываете QueryInterface, чтобы получить по m_pUnknownInner указатель на интерфейс IID_IY, эта функция, как примерный школьник, вызывает для возвращаемого указателя AddRef. Поскольку внутренний компонент агрегирован, он делегирует вызов AddRef внешнему IUnknown. В результате увеличивается счетчик ссылок внешнего компонента, а не внутреннего. Я хочу еще раз это подчеркнуть. Когда внешний компонент запрашивает интерфейс через указатель на неделегирующий IUnknown или какой-либо еще интерфейс внутреннего компонента, счетчик ссылок внешнего компонента увеличивается. Это именно то, что требуется, когда интерфейс через указатель на интерфейс внутреннего компонента запрашивает клиент. Но в данном случае указатель на интерфейс IY запрашивает внешний компонент, и счетчик ссылок для этого указателя является счетчиком ссылок внешнего компонента. Таким образом, внешний компонент удерживает одну ссылку сам на себя! Если допустить такое, счетчик ссылок внешнего компонента никогда не станет нулем, и компонент никогда не будет удален из памяти.
Так как время существования указателя на IY, принадлежащего внешнему компоненту, вложено во время существования самого внешнего компонента, нам нет необходимости увеличивать счетчик ссылок. Но не вызывайте для уменьшения счетчика ссылок Release для IY — мы обязаны работать с интерфейсом IY так, как если бы у него был отдельный счетчик ссылок. (В нашей реализации компонента неважно, для какого указателя вызывать Release. Но в других случаях это может быть не так.) Освобождение интерфейса IY может освободить используемые им ресурсы. Следовательно, общее правило состоит в том, чтобы вызывать Release, используя указатель, переданный в CoCreateInstance. Версия CA::Init, запрашивающая интерфейс IY, приведена ниже:
HRESULT __stdcall CA::Init()
{
//Получить указатель на внешний IUnknown IUnknown* pUnknownOuter = this;
//Создать внутренний компонент
HRESULT hr = CoCreateInstance(CLSID_Component2,
PUnknownOuter, |
// IUnknown внешнего компонента |
CLSCTX_INPROC_SERVER, |
|
IID_IUnknown, |
// При агрегировании только IUnknown |
(void**)&m_pUnknownInner);
if (FAILED(hr))
120
{
// Ошибка при создании компонента return E_FAIL;
}
//Этот вызов увеличит счетчик ссылок внешнего компонента
//Получить интерфейс IY внутреннего компонента
hr = m_pUnknownInner->QueryInterface(IID_IY, (void**)&m_pIY); if (FAILED(hr))
{
// Внутренний компонент не поддерживает интерфейс IY m_pUnknownInner->Release();
return E_FAIL;
}
//Нам нужно уменьшить счетчик ссылок на внешний компонент,
//увеличенный предыдущим вызовом
pUnknownOuter->Release(); return S_OK;
}
При реализации QueryInterface у нас есть выбор — либо возвращать m_pIY, либо вызывать QueryInterface внутреннего компонента. Поэтому можно использовать либо
else if (iid == IID_IY)
{
return m_pUnknownOuter->QueryInterface(iid, ppv);
}
как мы это делали, либо
else if (iid == IID_IY)
{
*ppv = m_pIY;
}
Итак, мы уже создали внутренний компонент, запросили у него интерфейс, скорректировали счетчик ссылок и вернули интерфейс клиенту. Чего мы еще не сделали, так это не освободили интерфейс в деструкторе внешнего компонента. Мы не можем просто вызвать m_pIY->Release, так как у нас нет для него подсчитанной ссылки. Мы убрали ее в функции Init внешнего компонента после того, как получили указатель на IY. Теперь необходимо повторить процедуру в обратном порядке, восстановив счетчик ссылок и вызвав Release для указателя на IY. Однако здесь следует быть осторожным, так как в противном случае этот последний вызов Release снова сделает счетчик ссылок внешнего компонента нулевым, и тот попытается удалить себя.
Таким образом, процесс освобождения нашего указателя на интерфейс внутреннего компонента состоит из трех этапов. Во-первых, необходимо гарантировать, что наш компонент не попытается снова удалить себя. Во-вторых, необходимо вызвать AddRef для внешнего компонента, так как любой вызов Release для внутреннего компонента вызовет Release для внешнего. И, наконец, мы можем освободить указатель на IY, хранящийся во внешнем компоненте. Соответствующий код приведен ниже:
//1. Увеличить счетчик ссылок во избежание
//рекурсивного вызова деструктора
m_cRef = 1;
//2. AddRef для внешнего IUnknown IUnknown* pUnknownOuter = this; pUnknownOuter->AddRef();
//3. Освободить интерфейс
m_pIY->Release();
Давайте мысленно проследим работу этого кода внешнего компонента. Первое, что мы делаем, — устанавливаем счетчик ссылок в 1. Далее мы увеличиваем его до двух. Затем вызываем Release для интерфейса IY. Внутренний компонент будет делегировать этот вызов внешнему. Последний уменьшит счетчик ссылок с 2 до 1. Если бы мы не установили ранее счетчик ссылок в 1, то компонент попытался бы во второй раз удалить себя.
В нашей реализации, когда внутренний компонент агрегируется, его функция Release просто делегируется внешнему IUnknown. Однако внешний компонент должен работать с внутренним так, как если бы тот вел отдельные счетчики ссылок для каждого интерфейса, поскольку другие реализации внутреннего компонента могут делать больше, чем просто делегировать Release внешнему компоненту. Внутренний компонент может также освобождать некоторые ресурсы или выполнять другие операции.