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

173

Цикл сообщений цикл сообщений цикл сообщений…

В программах на С и С++ есть стандартная точка входа, которая называется main. С функции main начинается выполнение программы. Программа завершает работу, когда происходит возврат из main. Точно так же в программах для Windows есть функция WinMain. Таким образом, чтобы модуль EXE не прекращал работу, необходим цикл, предотвращающий выход из main или WinMain. Так как наш сервер компонента работает под Windows, я добавил цикл выборки сообщений Windows. Он представляет собой упрощенную версию цикла, используемого всеми программами для Windows.

Код цикла выборки сообщений содержится в файле OUTPROC.CPP. Компиляция данного файла и компоновка с ним происходят только в том случае, если собирается версия сервера вне процесса.

Подсчет пользователей

Помните, как мы запускали сервер перед запуском клиента? После завершения работы клиента сервер оставался загруженным. Пользователи сервера — также клиенты, и у них должен быть свой счетчик блокировок. Поэтому, когда пользователь создает компонент, мы увеличиваем CFactory::s_cServerLocks. Таким образом, сервер будет оставаться в памяти, пока с ним работает пользователь.

Как нам определить, что сервер запустил пользователь, а не библиотека СОМ? Когда CoGetClassObject загружает EXE локального сервера, она задает в командной строке аргумент Embedding. EXE проверяет наличие этого аргумента в командной строке. Если Embedding там нет, то сервер увеличивает s_cServerLocks и отображает окно для пользователя.

Когда пользователь завершает работу сервера, с тем по-прежнему могут работать клиенты. Следовательно, когда пользователь завершает программу, сервер должен убрать с экрана пользовательский интерфейс, но не завершаться, пока не закончит обслуживание всех клиентов. Таким образом, сервер не должен посылать себе сообщение WM_QUIT при обработке сообщения WM_DESTROY, если только CanUnloadNow не возвращает S_OK. Вы можете сами посмотреть на соответствующий код в OUTPROC.CPP.

Удаленный сервер

Самое замечательное в локальном сервере, который мы реализовали в этой главе, — то, что он является и удаленным сервером. Без каких-либо изменений CLIENT.EXE и SERVER.EXE могут работать друг с другом по сети. Для этого Вам потребуется по крайней мере два компьютера, на которых работает Windows NT 4.0 или Windows 95 с установленной поддержкой DCOM. Естественно, эти компьютеры должны быть соединены между собой сетью.

Чтобы заставить клиента использовать удаленный сервер, воспользуемся программой конфигурации DCOMDCOMCNFG.EXE, которая входит в состав Windows NT. Эта программа позволяет изменять различные параметры приложений, установленных на компьютере, в том числе и то, исполняются ли они локально или удаленно.

В табл. 10-2 представлены пошаговые инструкции для выполнения SERVER.EXE в удаленном режиме.

Таблица 10-2 Запуск SERVER.EXE на удаленной машине

Действие

Локальный

Удаленный

 

компьютер

компьютер

 

 

 

Скомпонуйте CLIENT, SERVER.EXE и PROXY.DLL с помощью

#

 

команды nmake-f makefile. Если Вы уже их скомпоновали, делать это

 

 

заново не нужно. (Я компоновал программы на компьютере с

 

 

Windows 95 и затем копировал на компьютер с Windows NT.)

 

 

Скопируйте CLIENT.EXE, SERVER.EXE и PROXY.DLL на

 

#

удаленный компьютер.

 

 

Зарегистрируйте локальный сервер с помощью команды server

#

#

/RegServer.

 

 

Зарегистрируйте заместитель с помощью команды regsvr32 Proxy.dll.

#

#

Запустите CLIENT.EXE и выберите вариант локального сервера. Это

#

#

позволит Вам убедиться, что программы работают на обоих

 

 

компьютерах.

 

 

Запустите DCOMCNFG.EXE. Выберите компонент Inside COM

#

 

Chapter 10 Example Component 1 и щелкните Properties. Выберите

 

 

174

Действие

Локальный

Удаленный

 

компьютер

компьютер

 

 

 

вкладкуLocation. Отключите опцию Run Application On This Computer

 

 

и выберите опцию Run Application On Following Computer. Введите

 

 

имя удаленного компьютера, на котором будет выполняться

 

 

SERVER.EXE.

 

 

Щелкните вкладку Identity и выберите кнопку-переключатель

#

 

Interactive User.

 

 

В зависимости от Ваших прав доступа может потребоваться изменить

#

#

установки на вкладке Security.

 

 

Запустите SERVER.EXE, чтобы увидеть его вывод на экран.

 

#

Запустите CLIENT.EXE и выберите вариант 2, чтобы использовать

#

 

локальный сервер компонента.

 

 

В окне SERVER.EXE должны появиться сообщения.

 

#

Сообщения также должны появиться в консольном окне CLIENT.EXE.

#

 

 

 

 

Я нахожу поистине восхитительным, что с помощью служебной программы мы можем превратить локальный сервер в удаленный. Остается вопрос — как это работает?

Что делает DCOMCNFG.EXE?

Если после запуска DCOMCNFG.EXE Вы запустите на той же машине REGEDIT.EXE, то сможите увидеть часть этого волшебства в Реестре. Найдите следующий раздел Реестра:

HKEY_CLASSES_ROOT\ CLSID\

{0C092C29-882C-11CA-A6BB-0080C7B2D682}

В дополнение к дружественному имени компонента Вы увидите новое значение с именем AppID. CLSID идентифицирует компонент, и соответствующий раздел Реестра содержит информацию о нем. В разделе LocalServer32 указан путь к приложению, в котором реализован компонент, но CLSID никак больше не связан с приложением. Однако DCOM нужно связать с приложением, содержащим компонент, определенную информацию. Для этого используется AppID.

Значением AppID, также как и CLSID, является GUID. Информация об AppID хранится в ветви Реестра AppID; и здесь аналогично CLSID. Информацию об AppID для SERVER.EXE можно найти в разделе Реестра:

HKEY_CLASSES_ROOT\ AppID\

{0C092C29-882C-11CA-A6BB-0080C7B2D682}

В разделе для AppID хранятся как минимум три значения. Значение по умолчанию — дружественное имя. Другие именованные значения — RemoteServerName, задающее имя сервера, на котором находится приложение, и RunAs, сообщающее DCOM, как исполнять приложение. Соответствующая структура Реестра показана на рис. 10-6.

Кроме этого, непосредственно в разделе AppID хранится имя приложения. Вы должны увидеть в Редакторе Реестра такой раздел:

HKEY_CLASSES_ROOT\ AppID\

server.exe

В нем только одно именованное значение, которое указывает обратно на AppID.

Но как это работает?

Внесение записей в Реестр дает мало пользы до тех пор, пока у нас нет кода, который их читает. DCOM расширяет библиотеку СОМ, включая в нее свою реализацию функции CoGetClassObject. Эта функция не только гораздо мощнее, но и гораздо запутанней. CoGetClassObject может работать множеством разных способов. Обычно она принимает CLSID и открывает сервер компонента в соответствующем контексте. Если контекстом является CLSCTX_REMOTE_SERVER, CoGetClassObject отыскивает компонент в Реестре и проверяет, задан ли для него AppID. В этом случае функция отыскивает в Реестре значение RemoteServerName. Если имя сервера найдено, то CoGetClassObject пытается запустить сервер удаленно. Именно это и происходило в примере выше.

175

HKEY_CLASSES_ROOT

AppID

{0C092C29-882C-11CF-A6BB-0080C7B2D682}

 

RemoteServerName

 

"My Remote Server"

RunAs

"Interactive User"

server.exe

AppID

{0C092C29-882C-11CF-A6BB-0080C7B2D682}

 

CLSID

{0C092C29-882C-11CF-A6BB-

AppID

0080C7B2D682}

{0C092C29-882C-11CF-A6BB-0080C7B2D682}

Рис. 10-6 Организация записей Реестра для AppID.

Другая информация DCOM

Хотя, перемещаясь по Реестру, можно превратить локальный сервер в удаленный, можно также программно указать, что Вам нужен доступ к удаленному серверу. Для этого следует заменить CoCreateInstance на CoCreateInstanceEx или модифицировать вызов CoGetObject. Ниже приведен пример использования CoCreateInstanceEx для создания удаленного компонента:

// Создать структуру для хранения информации о сервере.

COSERVERINFO ServerInfo;

//Инициализировать структуру нулями. memset(&ServerInfo, 0, sizeof(ServerInfo));

//Задать имя удаленного сервера.

ServerInfo.pwszName = L”MyRemoveServer”;

//Подготовить структуры MULTI_QI для нужных нам интерфейсов.

MULTI_QI mqi[3];

mqi[0].pIID = IIDX_IX; // [in] IID требуемого интерфейса mqi[0].pItf = NULL; // [out] Указатель интерфейса

mqi[0].hr = S_OK; // [out] Результат вызова QI для интерфейса mqi[1].pIID = IIDX_IY;

mqi[1].pItf = NULL; mqi[1].hr = S_OK; mqi[2].pIID = IIDX_IZ; mqi[2].pItf = NULL; mqi[2].hr = S_OK;

HRESULT hr = CoCreateInstanceEx(CLSID_Component1, NULL, CLSCTX_REMOTE_SERVER,

&ServerInfo,

3, // Число интерфейсов

&mqi);

Первое бросающееся в глаза отличие CoCreateInstanceEx от CoCreateInstance — то, что первая функция принимаетв качестве параметра структуру COMSERVERINFO, содержащую имя удаленного сервера. Однако самый интересный аспект CoCreateInstanceEx — структура MULTI_QI.

MUTI_QI

Для компонентов внутри процесса вызовы QueryInterface выполняются очень быстро. QueryInterface достаточно быстро работает и для локальных серверов. Но когда необходимо пересылать информацию по сети, накладные расходы на любой вызов функции значительно возрастают. Работа приложения может и вовсе застопориться в результате повторяющихся вызовов, включая вызовы QueryInterface. В связи с этим для сокращения накладных

176

расходов на вызовы QueryInterface в DCOM определена новая структура с именем MULTI_QI. Эта структура позволяет выполнять запрос нескольких интерфейсов за один раз, что может существенно уменьшить накладные расходы.

В приведенном выше пример мы запрашиваем интерфейсы IX,IY и IZ одновременно. CoCreateInstanceEx возвращает S_OK, если ей удалось получить все интерфейсы, заданные структурами MULTI_QI. Она возвращает E_NOINTERFACE, если не удалось получить ни одно интерфейса. Если же получены некоторые, но не все требуемые интерфейсы, возвращается CO_S_NOTALLINTERFACES.

Код ошибки, связанный с каждым отдельным интерфейсом, записывается в поле hr структуры MULTI_QI. Указатель на интерфейс возвращается в поле pItf.

Чтобы запросить несколько интерфейсов, CoCreateInstanceEx запрашивает у компонента после его создания интерфейс ImultiQI. Он объявлен так:

interface IMultiQI : IUnknown

{

virtual HRESULT __stdcall QueryMultipleInterfaces (ULONG interfaces,

MULTI_QI* pMQUIs);

};

Самое замечательное то, что Вам не нужно реализовывать IMultiQI для своего компонента. Удаленный заместитель компонента предоставляет этот интерфейс автоматически.

CoCreateInstance не работает под Windows 95

Если Вы определите символ препроцессора

_WIN32_DCOM

или

_WIN32_WINNT >= 0x0400,

то значения CSLCTX_ALL и CLSCTX_SERVER будут включать в себя CLSCTX_REMOTE_SERVER и не будут работать на системах Windows 95, где не установлена поддержка DCOM. Если Вы разрабатываете программу для Windows 95 или Windows NT 3.51, убедитесь, что эти символы не определены.

Определение наличия DCOM

Для того, чтобы определить доступность сервисов DCOM, сначала проверьте, поддерживает ли OLE32.DLL свободные потоки. Если Ваша программа компонуется с OLE32.DLL статически, поступайте так:

if (GetProcAddress(GetModuleHandle(“OLE32”), “CoInitializeEx”) != NULL)

{

// Свободные потоки поддерживаются.

}

Если Вы загружаете OLE32.DLL динамически, используйте следующий фрагмент кода:

hmodOLE32 = LoadLibrary(“OLE32.DLL”);

if (GetProcAddress(hmodOLE32, “CoInitializeEx”) != NULL)

{

// Свободные потоки поддерживаются.

}

Определив, что в системе имеется поддержка свободных потоков, проверьте, включена ли DCOM:

HKEY hKEY;

LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, “SOFTWARE\\Microsoft\\Ole”, 0,

KEY_ALL_ACCESS, &hKey);

assert(lResult == ERROR_SUCCESS);

char rgch[2];

DWORD cb = sizeof(rgch);

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