Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
411
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

168

Регистрация DLL заместителя/заглушки

Обратите внимание, что код make-файла определяет символ REGISTER_PROXY_DLL при компиляции файлов DLLDATA.C и PROXY.C. В результате генерируется код, позволяющий DLL заместителя/заглушки выполнять саморегистрацию. Затем, после компоновки DLL заместителя, make-файл регистрирует ее. Тем самым гарантируется, что Вы не забудете зарегистрировать DLL заместителя. Если бы Вы забыли это сделать, то несколько часов удивлялись бы, отчего вдруг не работает программа. Я это испытал.

Что именно DLL заместителя/заглушки помещает в Реестр? Давайте рассмотрим наш пример. Убедитесь, что Вы скомпоновали программу; код make-файла автоматически регистрирует заместитель и сервер, так что Вам делать это нет необходимости. Или же запустите файл REGISTER.BAT для регистрации скомпилированной ранее версии программы.

Теперь давайте запустим старый верный REGEDIT.EXE и посмотрим на раздел Реестра:

HKEY_CLASSES_ROOT\

Interface\

{32BB8323-B41B-11CF-A6BB-0080C7B2D682}

Приведенный выше GUID — это IID интерфейса IX. В этом разделе содержится несколько записей. Самая для нас интересная — ProxyStubClsid32. В этом разделе содержится CLSID DLL заместителя/заглушки интерфейса; для интерфейсов IX, IY и IZ он совпадает. Если найдете этот CLSID в разделе HKEY_CLASSES_ROOT\CLSID, там можно обнаружить и подраздел InprocServer32, который указывает на PROXY.DLL. Как видите, интерфейсы регистрируются независимо от реализующих их компонентов (рис. 10-5).

HKEY_CLASSES_ROOT

CLSID

 

{32BB8323-B41B-11CF-A6BB-0080C7B2D682}

PSFactoryBuffer

InprocServer32

C:\Chap10\proxy.dll

Interface

 

{32BB8323-B41B-11CF-A6BB-0080C7B2D682}

IX

ProxyStubClsid32

{32BB8323-B41B-11CF-A6BB-0080C7B2D682}

Рис. 10-5 Структура информации, добавляемой в Реестр кодом заместителя/заглушки, сгенерированным MIDL

При помощи MIDL мы можем вызывать функции и выполнять маршалинг параметров через границы процессов — и все будет выглядеть так же, как и при вызове компонента внутри процесса.

Реализация локального сервера

Теперь пришло время рассмотреть изменения в CFactory, необходимые для поддержки серверов вне процесса. Всякий раз, пересекая границу, Вы должны быть готовы изменить свои привычки и поведение, чтобы соответствовать местным обычаям. Точно так же обслуживание компонента из EXE отличается от обслуживания компонента из DLL. Поэтому мы должны изменить CFactory, чтобы она обслуживала как компоненты в DLL, так и компоненты в EXE. Мы также внесем небольшие изменения в CUnknown. Однако код самих компонентов останется тем же самым.

В коде используется символ _OUTPROC_SERVER_, помечающий фрагменты, специфичные для локальных серверов (когда символ определен) или для серверов внутри процесса (когда он не определен). Прежде чем перейти к рассмотрению изменений в CFactory, давайте запустим пример программы.

169

Работа примера программы

При запуске клиент запросит Вас, хотите ли Вы использовать версию компонента для сервера внутри или вне процесса. Для подключения к компоненту внутри процесса клиент использует CLSCTX_INPROC_SERVER, а для подключения к компоненту вне процесса — CLSCTX_LOCAL_SERVER.

Если Вы решите использовать компонент, реализованный сервером внутри процесса, то все будет работать в точности, как в предыдущей главе. Однако если Вы выберете сервер вне процесса, программа будет работать несколько иначе. Первое, что Вы заметите, — вывод на экран теперь идет только от клиента. Это связано с тем, что компонент в другом процессе использует не то консольное окно, что клиент.

Вместо того, чтобы просто запустить клиент, сначала запустим сервер из командной строки. Дважды щелкните значок SERVER.EXE или воспользуйтесь командой start:

C:\>start server

Сервер начнет выполняться, и на экране появится его окно. Теперь запустите клиент и прикажите ему подключиться к локальному серверу. Клиент будет посылать сой вывод в новое консольное окно, а вывод локального сервера пойдет в его собственное окно.

Нет точек входа

Давайте теперь демистифицируем поведение этого примера. EXE не могут экспортировать функции. Наши серверы внутри процесса зависелт от наличия следующих экспортированных функций:

DllCanUnloadNow

DllRegisterServer

DllUnregisterServer

DllGetClassObject

Теперь нам нужна замена для этих функций. Заменить DllCanUnloadNow легко. EXE, в отличие от DLL, не является пассивным модулем — он управляет своей жизнью сам. EXE может отслеживать счетчик блокировок и , когда тот станет равным 0, выгрузить себя. Следовательно, для EXE нет необходимости реализовывать DllCanUnloadNow. Вычеркиваем ее из списка.

Следующие две функции — DllRegisterServer и DllUnregisterServer — заменить почти так же просто. EXE поддерживают саморегистрацию путем обработки параметров командной строки RegServer и UnRegServer. Все, что должен сделать наш локальный сервер, — это при получении соответствующего параметра командной строки вызвать CFactory::RegisterAll или CFactory::UnregisterAll. Пример кода, выполняющего эти действия, можно найти в файле OUTPROC.CPP. (Попутно замечу, что локальный сервер регистрирует местоположение своего EXE в разделе LocalServer32, а не в разделе InprocServer32. Вы можете заметить соответствующее изменение в файле REGISTRY.CPP.)

Таким образом, у нас осталась только DllClassObject, заменить которую несколько труднее, чем остальные функции, экспортируемые DLL.

Запуск фабрик класса

Возвращаясь к гл. 7, вспомните, что CoCreateInstance вызывает CoGetClassObject, которая вызывает DllGelClassObject. Последняя возвращает указатель на IClassFactory, который используется для создания компонента. Поскольку EXE не могут экспортировать DllGetClassObject, нужен другой способ передачи

CoGetClassObject нашего указателя на IClassFactory.

Решение, предлагаемое СОМ, — поддержка внутренней таблицы зарегистрированных фабрик класса. Когда клиент вызывает CoGetClassObject с соответствующими параметрами, СОМ сначала просматривает свою внутреннюю таблицу фабрик класса, ища заданный клиентом CLSID. Если фабрика класса в таблице отсутствует, то СОМ обращается к Реестру и запускает соответствующий модуль EXE. Задача последнего — как можно скорее зарегистрировать свои фабрики класса, чтобы их могла найти СОМ. Для регистрации фабрики класса EXE использует функцию СОМ CoRegisterClassObject. При запуске EXE обязан зарегистрировать все поддерживаемые им фабрики. Я добавил в CFactory новую стратегическую функцию-член StartFactories, которая вызывает CoRegisterClassObject для каждого компонента в массиве структур CFactoryData. Код этой функции приведен ниже.

BOOL CFactory::StartFactories()

{

CFactoryData* pStart = &g_FactoryDataArray[0]; const CFactoryData* pEnd =

&g_FactoryDataArray[g_cFactoryDataEntries – 1];

170

for(CFactoryData* pData = pStart; pData <= pEnd; pData++)

{

//Инициализировать указатель и признак фабрики класса pData->m_pIClassFactory = NULL;

pData->m_dwRegister = NULL;

//Создать фабрику класса для компонента

IClassFactory* pIFactory = new CFactory(pData);

//Зарегистрировать фабрику класса

DWORD dwRegister;

HRESULT hr = ::CoRegisterClassObject( *pData->m_pCLSID, static_cast<IUnknown*>(pIFactory), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,

&dwRegister);

if (FAILED(hr))

{

pIFactory->Release(); return FALSE;

}

// Запомнить информацию pData->m_pIClassFactory = pIFactory; pData->m_dwRegister = dwRegister;

}

return TRUE;

}

Данный код использует две новых переменных-члена, которые я добавил в класс CfactoryData. Переменная m_pIClassFactory содержит указатель на работающую фабрику класса для CLSID, хранящегося в m_pCLSID. Переменная m_dwRegister содержит магический признак (cookie)1 для данной фабрики.

Как видите, для регистрации фабрики класса нужно лишь ее создать и передать указатель на ее интерфейс функции CoRegisterClassObject. Значение большинства параметров CoRegisterClassObject легко понять из приведенного выше кода. Сначала идет ссылка на CLSID регистрируемого класса, за которой следует указатель на фабрику класса. Магический признак возвращается через последний параметр; он используется для отзыва фабрики класса с помощью функции CoRevokeClassObject. Третий и четвертый параметр — это флажки,

управляющие поведением CoRegisterClassObject.

Флажки для CoRegisterClassObject

Третий и четвертый параметр этой функции используются вместе, и смысл одного изменяется в зависимости от значения другого. В результате интерпретация становиться весьма запутанной.

Четвертый параметр указывает, может ли один экземпляр данного EXE обслуживать более одного экземпляра соответствующего компонента. Проще всего это понять, сравнив сервер EXE с приложением SDI (single document interface — однодокументный интерфейс). Для загрузки нескольких документов необходимо запустить несколько экземпляров такого приложения, тогда как один экземпляр приложения MDI (multiple document interface — многодокументный интерфейс) может открыть несколько документов. Если Ваш сервер EXE похож на приложение SDI, в том смысле, что он может обслуживать только один компонент, следует задать

REGCLS_SINGLEUSE и CLSCTX_LOCAL_SERVER.

Если сервер EXE может поддерживать несколько экземпляров компонента, подобно тому, как приложение MDI может открыть несколько документов, используйте REGCLS_MULTI_SEPARATE:

hr = ::CoRegisterClassObject(clsid, pUnknown, CLSCTX_LOCAL_SERVER, REGCLS_MULTI_SEPARATE,

&dwRegister);

1 Кто-то сказал мне, что «cookie» — это не термин информатики, а термин Microsoft. Я не знаю, что это такое, особенно учитывая, что большинство программ просмотра Web оставляют на вашем жестком диске файлы-«cookie». Как бы то ни было, мы в Microsoft используем этот термин для обозначения структуры данных, которая что-либо идентифицирует. Клиент запрашивает у сервера ресурс. Сервер выдает ресурс и возвращает клиенту признак («cookie»), который клиент может в дальнейшем использовать для ссылки на этот ресурс. С точки зрения клиента, «cookie» — это случайное число, смысл которого известен только серверу.

171

Возникает интересная ситуация. Предположим, что наш EXE-модуль зарегистрировал несколько компонентов. Пусть, кроме того, этому EXE необходимо использовать один из зарегистрированных им компонентов. Если соответствующая фабрика класса зарегистрирована с помощью приведенного выше оператора, то для обслуживания компонента будет запущен еще один экземпляр EXE. Очевидно, что в большинстве случаев это не столь эффективно, как мы бы хотели. Для регистрации сервера EXE как сервера своих собственных компонентов внутри процесса, объедините, как показано ниже, флаг CLSCTX_LOCAL_SERVER с флагом

CLSCTX_INPROG_SERVER:

hr = ::CoRegisterClassObject(clsid, pUnknow, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, REGCLS_MULTI_SEPARATE,

&dwRegister);

В результате объединения флажков сервер EXE сможет самостоятельно обслуживать свои компоненты. Поскольку данный случай наиболее распространен, для автоматического включения CLSCTX_INPROC_SERVER при заданном CLSCTX_LOCAL_SERVER используется специальный флаг REGCLS_MULTIPLEUSE. Ниже приведен эквивалент предыдущего вызова:

hr = ::CoRegisterClassObject(clsid, pUnknown, CLS_LOCAL_SERVER,

REGCLS_MULTIPLEUSE, &dwRegister);

изменив пример программы, можно увидеть различие между REGCLS_MULTIPLEUSE и REGCLS_MULTI_SEPARATE. Сначала удалите информацию сервера внутри процесса из Реестра следующей командой:

regsvr32 /u server.dll

Это гарантирует, что единственным доступным сервером будет локальный. Затем запустите клиент и выберите второй вариант для активации локального сервера. Локальный сервер будет прекрасно работать. Обратите внимание, что в функциях Unit в файлах CMPNT1.CPP и CMPNT2.CPP мы создаем компонент, используя CLSCTX_INPROC_SERVER, — но ведь мы только что удалили информацию сервера внутри процесса из Реестра! Следовательно, наш EXE сам предоставляет себе внутрипроцессные версии этих компонентов.

Теперь заменим REGCLS_MULTIPLEUSE на REGCLS_MULTU_SEPARATE и CFactory::StartFactories. (Строки,

которые нужно изменить, помечены в CFACTORY.CPP символами @Multi.) Скомпонуйте клиент и сервер заново, запустите клиент и выберите второй вариант. Вызов создания компонента потерпит неудачу, так как создания внутренних компонентов нет сервера внутри процесса, а REGCLS_MULTI_SEPARATE заставляет СОМ отвергать попытки сервера самостоятельно обслуживать компоненты внутри процесса.

Остановка фабрик класса

Когда работа сервера завершается, фабрики класса следует удалить из внутренней таблицы СОМ. Это выполняется при помощи функции библиотеки СОМ CoRevokeClassObject. Метод StopFactories класса CFactory вызывает CoRevokeClassObject для всех поддерживаемых данных EXE фабрик класса:

void CFactory::StopFactories()

{

CFactoryData* pStart = &g_FactoryDataArray[0]; const CFactoryData* pEnd =

&g_FactoryDataArray[g_cFactoryDataEntries – 1];

for (CFactoryData* pData = pStart; pData <= pEnd; pData++)

{

// Прекратить работу фабрики класса с помощью магического признака.

DWORD dwRegister = pData->m_dwRegister; if (dwRegister != 0)

{

::CoRevokeClassObject(dwRegister);

}

//Освободить фабрику класса.

IClassFactory* pIFactory = pData->m_pIClassFactory; if (pIfactory != NULL)

{

pIFactory->Release();

}

}

}

Соседние файлы в предмете Программирование на C++