- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
204
Разделенный — свободный
Если клиент в разделенном потоке вызывает свободный поток, то СОМ не выполняет синхронизацию вызова. Эта обязанность лежит на компоненте свободного потока. Маршалинг интерфейса выполняется, но, если оба потока принадлежат одному процессу, СОМ может оптимизировать маршалинг, чтобы передавать указатели клиенту непосредственно. Подробнее мы рассмотрим это при реализации свободных потоков.
Как Вы видите, потоковые модели компонентов СОМ не слишком отличаются от обычных моделей потоков Win32. В процессе может быть любое число потоков. Эти потоки могут быть разделенными или свободными. С точки зрения программиста, в модели потоков СОМ есть только два интересных момента: синхронизация и маршалинг. СОМ синхронизирует вызовы компонентов в разделенных потоках. Разработчик синхронизирует вызовы компонентов в свободных потоках. Синхронизация компонентов в свободных потоках — это общая проблема многопоточности, а не специфика СОМ. Однако маршалинг специфичен для СОМ — и это единственное, что действительно уникально при работе с компонентами СОМ в многопоточной среде. Подробно мы рассмотрим ручной маршалинг интерфейсов позже, когда реализуем разделенный поток и поток клиента.
Реализация модели разделенных потоков
С компонентами внутри подразделений хорошо то, что им нет необходимости быть «потокобезопасными». Доступ к ним синхронизируется СОМ. При этом ни имеет значения, приходит ли вызов из потоков других подразделений или из свободных потоков. СОМ автоматически использует скрытую очередь сообщений Windows для синхронизации клиентских вызовов таких компонентов. Благодаря этому реализация компонентов в однопоточных подразделениях очень схожа с написанием оконных процедур. (Для синхронизации доступа к оконной процедуре используется цикл выборки сообщений; СОМ использует тот же механизм для синхронизации доступа к однопоточному подразделению.) Ниже следуют основные требования к подразделению:
!" Оно должно вызывать CoInitialize или OleInitialize.
!" В нем может быть только один поток.
!" У него должен быть цикл выборки сообщений.
!" Оно обязано выполнять маршалинг указателей на интерфейсы при передаче их другим подразделениям.
!" В случае компонента внутри процесса подразделение должно иметь «потокобезопасные» точки входа
DLL.
!" Ему может понадобиться «потокобезопасная» фабрика класса.
В следующих параграфах некоторые из этих требований рассматриваются подробнее.
Компонент может существовать только в одном потоке
Компонент в однопоточном подразделении должен выполняться в единственном потоке. Доступ к компоненту имеет только создавший его поток. Именно та работает оконная процедура — ее вызывает только поток, создавший данное окно. Так как доступ к компоненту возможен только из одного потока, такой компонент всегда выполняется в однопоточном подразделении и ему не нужно заботиться о синхронизации. Однако компонент должен защищать свои глобальные данные, поскольку он, как и оконная процедура, доступен для повторного входа (реентерабелен).
Необходим маршалинг интерфейсов через границы подразделений
Для вызовов из других потоков необходим маршалинг — чтобы вызовы выполнял тот же поток, в котором выполняется компонент. Так как в подразделении только один поток, все остальные потоки находятся вне подразделения. При вызове через границы подразделений всегда требуется маршалинг. Несколько более подробно маршалинг указателей на интерфейсы мы рассмотрим ниже.
Точки входа DLL должны быть «потокобезопасны»
Компоненту в однопоточном подразделении не нужно быть «потокобезопасным», так как доступ к нему возможен только из создавшего его потока. Однако «потокобезопасны» должны быть точки входа DLL, такие как
DllGetClassObject и DllCanUnloadNow. Функцию DllGetClassObject могут одновременно вызывать несколько клиентов из разных потоков. Чтобы сделать эти функции «потокобезопасными», убедитесь, что все совместно используемые данные защищены от параллельного доступа. В некоторых случаях это означает, что фабрика класса также должна быть «потокобезопасна».
205
Фабрикам класса может понадобиться «потокобезопасность»
Если для каждого компонента Вы создаете отдельную фабрику класса, такой фабрике «потокобезопасность» не требуется, поскольку доступ к ней возможен только для одного клиента. Но если DllGetClassObject создает одну фабрику класса, которая используется для порождения всех экземпляров компонента, Вы должны гарантировать «потокобезопасность» фабрики, поскольку к ней возможен одновременный доступ из разных потоков.
Компонент вне процесса может использовать один экземпляр фабрики класса для создания всех экземпляров компонента. Такая фабрика класса также должна быть «потокобезопасна». Обеспечение потокобезопасности большинства фабрик класса просто, так как они не изменяют никаких совместно используемых данных, кроме счетчика ссылок. Для защиты последних можно использовать InterlockedIncrement и InterlockedDecrement, что я и демонстрировал уже много лун тому назад в гл. 4.
Удовлетворяющий перечисленным требованиям компонент внутри процесса помечает в Реестре, что поддерживает модель разделенных потоков. О том, как компонент регистрирует свою потоковую модель, рассказывается в конце этой главы, в разделе «Информация о потоковой модели в Реестре». Теперь же мы детально рассмотрим, что необходимо сделать для маршалинга указателя на интерфейс, который передается другому потоку.
Когда компонент в разделенном потоке передает свой интерфейс компоненту в другом потоке, для этого интерфейса требуется маршалинг. Неважно, является ли другой поток разделенным или свободным, маршалинг всегда необходим.
Автоматический маршалинг
Во многих случаях СОМ автоматически выполняет маршалинг интерфейса. В гл. 10 мы рассматривали DLL заместителя/заглушки, которые осуществляют маршалинг интерфейсов между процессами. С точки зрения программиста, потоковая модель не влияет на использование этих DLL. Они автоматически позаботятся о маршалинге между процессами.
DLL заместителя/заглушки используются СОМ и для маршалинга интерфейсов между разделенным потоком и другими потоками в том же процессе. Таким образом, когда Вы обращаетесь к интерфейсу компонента в другом подразделении, СОМ автоматически выполняет этот вызов через заместитель, и происходит маршалинг интерфейса.
Ручной маршалинг
Итак, когда же программист должен выполнять маршалинг указателя интерфейса самостоятельно? В основном тогда, когда он пересекает границу подразделения без помощи СОМ.
Давайте рассмотрим два примера. Сначала пусть клиент создает разделенный поток, создающий компонент и управляющий им. Как в главном потоке, так и в потоке подразделения может потребоваться доступ к такому компоненту. У разделенного потока есть указатель на интерфейс компонента, поскольку этот поток его и создал. Главный поток не может использовать этот указатель напрямую, так как он (поток) находится за пределами подразделения, в котором был создан компонент. Для того, чтобы главный поток мог использовать компонент, разделенный поток должен выполнить маршалинг интерфейса и передать результаты главному потоку. Последний должен выполнить демаршалинг указателя на интерфейс перед использованием.
Второй случай имеет место тогда, когда фабрика класса компонента внутри процесса создает его экземпляры в разных потоках. Этот сценарий похож на предыдущий, но теперь создание компонента выполняет в разных потоках сервер (в предыдущем случае это делал клиент). Клиент вызывает CoCreateInstance, в результате чего запускается фабрика класса компонента. Когда клиент вызывает IClassFactory::CreateInstance, фабрика класса создает новый разделенный поток. Этот новый поток создает компонент. IClassFactory::CreateInstance должна вернуть клиенту указатель на интерфейс компонента. Но CreateInstance не может непосредственно передать клиенту указатель на интерфейс, созданный в новом подразделении, так как клиент находится в другом потоке. Таким образом, поток подразделения должен выполнить маршалинг указателя на интерфейс для CreateInstance, которая затем выполняет демаршалинг указателя и возвращает его клиенту.
Самое длинное имя API Win32
Теперь, когда мы узнали, где нужен маршалинг интерфейса, нам нужно знать, как его осуществлять. Вы можете выполнить всю работу сами при помощи функций CoMarshalInterface и CoUnMarshalInterface. Но если у Вас есть более интересные занятия, используйте вспомогательные функции с самыми длинными именами в API Win32,
CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream. (Если так пойдет и дальше, скоро имя функции будет занимать целый абзац.)
Использовать эти функции просто. Маршалинг указателя на интерфейс IX выполняется так:
206
IStream* pIStream = NULL;
HRESULT hr = CoMarshalInterThreadInterfaceInStream(
IID_IX, |
// ID интерфейса, |
маршалинг которого нужно выполнить |
|
pIX, |
// |
Интерфейс, для |
которого выполняется маршалинг |
&pIStream); |
// |
Поток, куда будут помещены результаты маршалинга |
Демаршалинг выполняется следующим образом:
IX* pIXmarshaled;
HRESULT hr = CoGetInterfaceAndReleaseStream(
pIStream, |
// Поток, содержащий |
интерфейс |
|
|
IID_IX, |
// ID демаршализуемого интерфейса |
интерфейс |
||
(void**)&pIXmarshaled); |
// Демаршализованный |
указатель |
на |
Все очень просто, не так ли? Это так просто потому, что СОМ незаметно для программиста и автоматически использует DLL заместителя/заглушки.
Настало время написать программу
До этого места данная глава носила весьма концептуальный характер, и тому была основательная причина: концепции здесь сложнее реализации. Давайте рассмотрим простой пример. Предположим, Вы хотите в фоновом режиме изменять счетчик в компоненте, и иногда обновлять значение счетчика, выводимое на дисплей. Если бы Вы писали нормальную программу Win32, то создали бы рабочий поток, который бы «фоном» изменял счетчик.
Здесь мы будем делать то же самое, но вместо рабочего потока используем разделенный поток. Главный поток создает разделенный поток. Разделенный поток создает компонент и периодически обновляет его счетчик. Этот поток будет передавать главному потоку указатель на интерфейс, чтобы главный поток мог получать и отображать значение счетчика. Все, как в обычном многопоточном программировании в Win32 — за исключением того, что поток подразделения:
!" Инициализирует библиотеку СОМ.
!" Имеет собственный цикл выборки сообщений.
!" Выполняет маршалинг интерфейса для передачи его обратно главному потоку. Компонент в точности похож на те, что мы писали ранее.
Теперь самая сложная часть в разработке однопоточного подразделения состоит в том, что у нас есть лишь концепция, а не код. Как обычно, Вы создаете поток. Как обычно, Вы создаете цикл выборки сообщений. Так как я хотел, чтобы подразделение выглядело более «настоящим», то создал для выполнения этих действий небольшой класс CSimpleApartment.
CSimpleApartment и CClientApartment
CSimpleApartment — это простой класс, инкапсулирующий создание компонента в другом потоке. Соответствующий код находится в файлах APART.H и APART.CPP в каталоге CHAP12\APT_THD на прилагающемся к книге диске. CSimpleApartment::StartThread запускает новый поток. CSimpleApartment::CreateComponent принимает CLSID компонента и создает его в потоке, запущенном
StartThread.
Именно здесь все становится интересным (или непонятным). CSimpleApartment охватывает оба потока. Часть CSimpleApartment вызывается первоначальным потоком, а другая часть — новым потоком. CSimpleApartment обеспечивает коммуникацию двух потоков. Поскольку CSimpleApartment::CreateComponent вызывается из первоначального потока, постольку она не может создать компонент непосредственно. Компонент надо создать в новом потоке. Поэтому CreateComponent использует событие, чтобы дать потоку нового подразделения сигнал к созданию компонента. Для собственно создания поток подразделения вызывает функцию
CreateComponentOnThread. CSimpleApartment::CreateComponentOnThread — это чисто виртуальная функция,
которую следует определить в производном классе. В этом первом примере производный класс CClientApartment реализует версию CreateComponentOnThread, которая создает компонент самым обычным способом — при помощи CoCreateInstance.
Пример с разделенным потоком
В табл. 12-1 показана структура вызовов функций в коде, который мы собираемся рассматривать. Весь код в правой части таблицы исполняется в разделенном потоке, созданном CSimpleApartment::StartThread.
207
Таблица 12-1 Структура вызовов функций в примере с разделенным потоком
Главный поток |
Разделенный поток |
|
|
|
|
WinMain |
CSimpleApartment |
CSimpleApartment |
|
|
|
InitializeApartment |
StartThread |
RealThreadProc |
|
|
ClassThreadProc |
|
|
CClientApartment::WorkerFunction |
|
CreateComponent |
CreateComponentOnThread |
|
|
CClientApartment::CreateComponentOnThread |
|
|
|
CSimpleApartment::StartThread
Все самое интересное начинается в CLIENT.CPP с функции InitializeApartment. Она вызывает CSimpleApartment::StartThread, реализация которой приведена ниже:
BOOL CSimpleApartment::StartThread(DWORD WaitTime)
{
if (IsThreadStarted())
{
return FALSE;
} |
|
// Создать поток |
// Защита по умолчанию |
m_hThread = ::CreateThread(NULL, |
|
0, |
// Размер стека по умолчанию |
RealThreadProc, |
|
(void*)this, |
// Создать приостановленный |
CREATE_SUSPENDED, |
|
&m_ThreadId); |
// поток |
// Получить идентификатор |
|
|
// потока |
if (m_hThread == NULL)
{
trace("StartThread не может создать поток", GetLastError()); return FALSE;
}
trace("StartThread успешно создала поток");
//Создать событие для выдачи потоку команды на создание компонента m_hCreateComponentEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hCreateComponentEvent == NULL)
{
return FALSE;
}
//Создать событие, которое сигнализируется потоком при завершении m_hComponentReadyEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hComponentReadyEvent == NULL)
{
return FALSE;
}
trace("StartThread успешно создала события");
//Инициализировать время ожидания m_WaitTime = WaitTime;
//Поток был создан приостановленным; запустить его
DWORD r = ResumeThread(m_hThread); assert(r != 0xffffffff);
//Дождаться начала выполнения потока перед продолжением
WaitWithMessageLoop(m_hComponentReadyEvent);
return TRUE;
}
208
CSimpleApartment::StartThread создает новый поток при помощи ::CreateThread. Она также создает два события для синхронизации двух потоков. Функция CSimpleApartment::ClassThreadProc, выполняющаяся в потоке подразделения, использует m_hComponentReadyEvent дважды — сначала для сигнализации о том, что новый поток начал выполняться, и в конце для сигнализации о том, что он остановлен. Функция
CSimpleApartment::CreateComponent использует событие m_hCreateComponentEvent, чтобы выдать потоку подразделения команду на вызов CSimpleApartment::CreateComponentOnThread для создания компонента. После создания компонента CreateComponentOnThread устанавливает m_hCreateComponentEvent, чтобы уведомить об окончании создания CreateComponent.
CSimpleApartment::WaitWithMessageLoop — это вспомогательная функция, которая ожидает события. Она не просто ждет, а обрабатывает события Windows. Если Вы будете ждать события без обработки сообщений, пользователю покажется, что программа «зависла». Пользовательский интерфейс должен всегда обрабатывать сообщения в процесса ожидания. WaitWithMessageLoop использует функцию API Win32
MsgWaitForMultipleObjects, которую мы рассмотрим ниже.
CSimpleApartment::ClassThreadProc
При запуске потока вызывается статическая функция RealThreadProc, которая вызывает ClassThreadProc. Windows не может вызывать функции С++, поэтому функции обратного вызова Win32 обязаны быть статическими. При создании потока его процедуре передается указатель нашего класса, чтобы она могла вызвать
ClassThreadProc. Код ClassThreadProc приведен ниже:
DWORD CSimpleApartment::ClassThreadProc()
{
// Инициализировать библиотеку СОМ
HRESULT hr = CoInitialize(NULL); if (SUCCEEDED(hr))
{
//Сигнализировать, что поток запущен
SetEvent(m_hComponentReadyEvent);
//Ждать команды на создание компонента
BOOL bContinue = TRUE; while (bContinue )
{
switch(::MsgWaitForMultipleObjects(
1, &m_hCreateComponentEvent, FALSE,
m_WaitTime, QS_ALLINPUT))
{
//Создать компонент case WAIT_OBJECT_0:
CreateComponentOnThread();
break;
//Обработать сообщения Windows case (WAIT_OBJECT_0 + 1):
MSG msg;
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
bContinue = FALSE; break;
}
DispatchMessage(&msg);
}
break;
// Выполнить фоновую обработку case WAIT_TIMEOUT:
WorkerFunction();
break;
default:
trace("Ошибка ожидания", GetLastError());
}
209
}
// Освободить библиотеку COM CoUninitialize();
}
// Сигнализировать о завершении потока
SetEvent(m_hComponentReadyEvent); return 0;
}
Подразделения должны инициализировать библиотеку СОМ и содержать циклы выборки сообщений. ClassThreadProc удовлетворяет этим требованиям. Вместо того, чтобы просто использовать цикл
GetMessage/DispatchMessage, ClassThreadProc использует MsgWaitForMultipleObjects, которая ожидает, пока не произойдет одно из трех событий: m_hCreateComponentEvent, сообщение Windows или истечение времени тайм-
аута. Если устанавливается событие m_hCreateComponentEvent, то MsgWaitForMultipleObjects прекращает ожидание и ClassThreadProc вызывает CreateComponentOnThread. Если потоку посылается сообщение Windows, то цикл PeekMessage/DispatchMessage извлекает и распределяет это сообщение (а также любые другие, находящиеся в очереди). Если истекает время тайм-аута, вызывается CSimpleApartment::WorkerFunction. Эта функция реализована производным классом CClientApartment, о котором мы поговорим ниже.
При желании Вы можете использовать GetMessage/DispatchMessage в чистом виде. Для выдачи команды на создание компонента вместо события можно использовать PostThreadMessage. Однако MsgWaitForMultipleObjects более эффективна.
CSimpleApartment::CreateComponent
Теперь, когда мы создали поток, пришла пора создавать компонент. Создание начинается с вызова главным потоком CSimpleApartment::CreateComponent. Код этой функции приведен ниже:
HRESULT CSimpleApartment::CreateComponent(const CLSID& clsid, const IID& iid, IUnknown** ppI)
{
// Инициализировать совместно используемые данные m_pIStream = NULL;
m_piid = &iid; m_pclsid = &clsid;
//Выдать потоку команду на создание компонента
SetEvent(m_hCreateComponentEvent);
//Ожидать завершения создания компонента
trace("Ожидать завершения создания компонента "); if (WaitWithMessageLoop(m_hComponentReadyEvent))
{
trace("Ожидание закончилось успешно");
if (FAILED(m_hr)) |
// Ошибка GetClassFactory? |
{ |
|
return m_hr; |
|
} |
|
if (m_pIStream == NULL) |
// Ошибка при маршалинге? |
{ |
|
return E_FAIL; |
|
} |
|
trace("Демаршалинг указателя на интерфейс"); // Выполнить демаршалинг интерфейса
HRESULT hr = ::CoGetInterfaceAndReleaseStream(m_pIStream, iid, (void**)ppI);
m_pIStream = NULL; if (FAILED(hr))
{
trace("Ошибка CoGetInterfaceAndReleaseStream", hr); return E_FAIL;
}
return S_OK;
}
210
trace("Что случилось?"); return E_FAIL;
}
Функция CreateComponent выполняет четыре основных действия. Во-первых, она копирует свои параметры в переменные-члены. Во-вторых, она выдает потоку команду на создание компонента. В-третьих, она ждет завершения создания компонента. И в-четвертых, она выполняет демаршалинг запрошенного интерфейса компонента.
CSimpleApartment::CreateComponentOnThread
Когда CreateComponent устанавливает m_hCreateComponentEvent, ClassThreadProc вызывает приватную внутреннюю версию CreateComponentOnThread, а та выполняет два основных действия. Во-первых, она вызывает чисто виртуальную версию CreateComponentOnThread с параметрами, которые были переданы CreateComponent. Непосредственная передача параметров CreateComponentOnThread упрощает ее реализацию в производном классе. Во-вторых, она выполняет маршалинг интерфейса:
void CSimpleApartment::CreateComponentOnThread()
{
IUnknown* pI = NULL;
// Вызвать производный класс для фактического создания компонента m_hr = CreateComponentOnThread(*m_pclsid, *m_piid, &pI);
if (SUCCEEDED(m_hr))
{
trace("Компонент создан успешно");
// Выполнить маршалинг интерфейса для основного потока
HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(*m_piid, pI,
&m_pIStream);
assert(SUCCEEDED(hr));
// Освободить указатель pI pI->Release();
}
else
{
trace("Ошибка CreateComponentOnThread", m_hr);
}
trace("Сигнализировать главному потоку, что компонент создан");
SetEvent(m_hComponentReadyEvent);
}
CreateComponentOnThread использует функцию CoMarshalInterThreadInterfaceInStream для маршалинга указателя на интерфейс в другой поток. Код CreateComponent выполняет демаршалинг интерфейса.
CClientApartment
В этом примере CClientApartment реализует две виртуальные функции: CreateComponentOnThread и WorkerFunction. CClientApartment предназначена для использования клиентами, которые хотят создавать компоненты в разных потоках. Она переопределяет CreateComponentOnThread, чтобы вызвать CoCreateInstance:
HRESULT CClientApartment::CreateComponentOnThread(const CLSID& clsid, const IID& iid, IUnknown** ppI)
{
HRESULT hr = ::CoCreateInstance(clsid, NULL,
CLSCTX_INPROC_SERVER, iid,
(void**)ppI);
if (SUCCEEDED(hr))
{
// Запросить интерфейс IX, который используется в WorkerFunction hr = (*ppI)->QueryInterface(IID_IX, (void**)&m_pIX);
if (FAILED(hr))
{
// Если мы не можем с этим работать, на дадим и другим
(*ppI)->Release(); return E_FAIL;