Distributed Component Object Model (DCOM) - это расширение технологии COM, позволяющее проектировать собственные компоненты, способные работать не только на одной машине, но и на разных компьютерах сети. Таким образом можно создавать распределенные приложения. Это может пригодиться, например, в локальной сети, когда один сервер обслуживает нескольких клиентов. Преимущества такого подхода очевидны.

MS Visual C++ позволяет создавать серверы COM в виде модулей dll или exe либо в виде службы NT.

В этой статье я расскажу о том, как создать сервер DCOM в виде dll и клиента для него. Предполагается, что читатели имеют представление о технологии COM, поэтому здесь будут затронуты только аспекты, специфичные для DCOM, - суррогатные процессы, маршаллинг, построение dll для заместителя/заглушки.

Я расскажу, как построить COM-сервер, реализующий интерфейс ICar с двумя методами - «добавить топливо» и «получить пробег», а также клиента для этого сервера.

Как работает DCOM-сервер в dll

Суррогатный процесс. Библиотеки dll не имеют своего процесса и подгружаются в клиентский процесс. А что происходит, если клиентский процесс работает на другом компьютере?

В этом случае библиотека COM, получив запрос на активизацию сервера, запускает так называемый суррогатный процесс и загружает в него dll сервера. По умолчанию суррогатный процесс предоставляется библиотекой COM, однако разработчик при желании может использовать другой (пользовательский) процесс.

Используемый по умолчанию суррогатный процесс (файл dllhost.exe) представляет собой псевдо-сервер СОМ со смешанной потоковой моделью. Когда несколько серверов dll загружаются в такой суррогатный процесс, библиотека СОМ гарантирует, что СОМ-объекты каждого сервера создаются с использованием той потоковой модели, которая назначена в реестре для этого сервера.

Все серверы с моделью свободных потоков будут функционировать в многопотоковом подразделении (apartment), тогда как однопотоковые серверы получат однопоточковые подразделения. Если СОМ-сервер поддерживает обе потоковые модели, то библиотека СОМ выбирает для такого сервера многопотоковую модель.

При использовании суррогатного процесса по умолчанию, библиотека СОМ управляет не только загрузкой СОМ-серверов, но и их выгрузкой из процесса и завершением самого процесса.

Маршаллинг. В модели DCOM сервер и клиент находятся на разных компью-терах сети. Следовательно, сервер и клиент работают в разных процессах и для взаимодействия друг с другом должны использовать маршаллинг.

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

В СОМ маршаллинг реализован с помощью так называемых «заместителя» и «заглушки» (proxy/stub), как показано на Рисунке 1.

Рисунок 1. Схема работы маршаллинга.

Заместитель (Proxy) - это загружаемый в процесс клиента СОМ-объект, который упаковывает вызовы клиента и передает их соответствующей заглушке по протоколу RPC.

Заглушка (Stub) - СОМ-объект, который получает упакованные вызовы клиента, распаковывает их и вызывает необходимые методы реального СОМ-объекта. Заглушка взаимодействует с соответствующим заместителем.

Можно сказать, что заместитель для клиента «притворяется» сервером, а заглушка для сервера «притворяется» клиентом.

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

Код заместителя и заглушки можно поместить в тот же dll, что и сам СОМ-сервер, а можно разместить в отдельном dll. Как и СОМ-сервер, dll заместителя/заглушки должен быть зарегистрирован, т. е. в системный реестр необходимо внести соответствующие записи (об этом чуть позже).

При создании удаленного СОМ-сервера удобно поместить код заглушки/заместителя в отдельный небольшой dll, чтобы не копировать на клиентский компьютер dll, содержащий код и сервера, и заместителя/заглушки.

Регистрация СОМ-сервера для работы в суррогатном процессе с удаленным клиентом.

Для того чтобы COM-сервер в библиотеке dll можно было вызвать удаленно и, следовательно, загрузить в суррогатный процесс, в системном реестре сервера и клиента должны присутствовать некоторые разделы и параметры.

1. В разделе HKEY_CLASSES_ROOTCLSID сервера должен быть подраздел с именем GUID СОМ-объекта. В нашем примере имя подраздела ?{0A08D45F-07CD-11D5-8720-00500433EEC4}?. В нем должны быть параметр AppID со значением GUID СОМ-объекта и подраздел InProc-Server32.

2. В разделе HKEY_CLASSES_ROOTAppID сервера должен присутствовать подраздел с именем GUID СОМ-объекта. В подразделе должен быть параметр с именем DllSurrogate и соответствующим значением - пустая строка либо путь к пользовательскому суррогатному процессу.

3. В разделе HKEY_CLASSES_ROOTInterface и на клиенте и на сервере должен присутствовать подраздел с именем GUID СОМ-объекта, а в нем - раздел с именем Proxy-StubClsid32, где хранится GUID заместителя/заглушки.

RGS-файл. В числе прочих файлов при создании COM-сервера, Visual C++ генерирует .rgs-файл. Этот файл содержит сценарий для внесения/удаления записей в системный реестр. Чтобы наш сервер мог работать с удаленным клиентом, необходимо внести некоторые изменения в файл сценария (см. Листинг 1). Выделенные строки добавлены вручную.

Файл .rgs построен по иерархическому принципу. Каждый узел обозначается парой фигурных скобок. Главный узел HKCR задает ветвь системного реестра HKEY_CLASSES_RO-OT, в который сценарий будет вносить изменения.

Оператор NoRemove раздела CLSID означает, что при внесении записей в CLSID все остальные подразделы остаются неизменными. Оператор For-ceRemove, напротив, удаляет все записи под заданным подразделом с последующим внесением новых записей.

Поясним смысл внесенных изменений. Строка

val AppID = s `{0A08D45F-07CD-11D5-8720
-00500433EEC4}`

добавляет раздел AppID в соответствующий раздел CLSID, а последние выделенные строки Листинга 1 добавляют параметр dllSurrogate в раздел AppID. Этот параметр указывает на то, что сервер с заданным идентификатором CLSID должен быть запущен в суррогатном процессе. Значение параметра ( ` ` ) - пустая строка - говорит о том, что будет использоваться суррогатный процесс по умолчанию. Если необходимо задействовать пользовательский суррогатный процесс, параметр dllSurrogate должен содержать путь к файлу пользовательского суррогатного процесса.

Настройки локальной сети. При запуске сервера на машине под Windows NT/2000 клиентский компьютер должен иметь достаточные полномочия для активизации сервера и работы с ним.

Создание сервера

Теперь мы знаем, как работает DCOM. Применим наши знания на практике. Для создания COM-компонентов с помощью MS Visual C++ 6.0 в нашем распоряжении имеется библиотека ATL. Воспользуемся ею и встроенными мастерами. В меню New-Projects следует выбрать ATL COM AppWizard (см. Экран 1).

Экран 1. Выбор проекта - ATL COM AppWizard.

На следующей вкладке выбираем тип нашего сервера - dll (см. Экран 2). Флажок Allow merging of proxy/stub code выставлять не рекомендуется. В этом случае код заглушки и заместителя будет помещен в отдельный dll, что удобно для регистрации заглушки/заместителя на удаленной клиентской машине.

Экран 2. Выбор типа COM-сервера - dll.

Далее нужно добавить в проект новый ATL-объект, выбрать Simple Object и создать интерфейс IСar. Теперь остается добавить методы интерфейса (см. Листинг 2).

Метод AddFuel («Добавить топливо»):

HRESULT AddFuel([in] long nFuelVolume).

Метод GetHaul («Получить пробег»):

HRESULT GetHaul([out] long * pnHaul).

Обратите внимание, что аргумент метода GetHaul имеет тип long*, так как это параметр типа out.

Построение и регистрация dll заместителя и заглушки. Для построения библиотеки dll заместителя/заглушки нужно воспользоваться утилитой NMAKE.EXE. Эта утилита позволяет построить проект на основании команд, содержащихся в файле описаний - .mk-файле.

Make-файл имеет имя «ИМЯ_ПРОЕКТАps.mk». Буквы «ps» в конце имени указывают на то, что это make-файл для proxy/stub dll. Visual Studio избавляет пользователя от рутинной работы и позволяет автоматизировать процесс создания и регистрации модуля dll заместителя/заглушки.

Экран 3. Изменение настроек серверного проекта для построения dll заместителя и заглушки.

Для этого на вкладке Project Settings->Post-build step (см. Экран 3) необходимо ввести две команды:

1) nmake /f DCOM_Serverps.mk

2) regsvr32 DCOM_Serverps.dll

Действие этих команд стоит распространить на все конфигурации проекта, установив предварительно All Configurations в списке Settings For. В результате работы утилиты NMAKE будет создана dll заместителя/заглушки с именем «ИМЯ_ПРОЕКТАps.dll». Утилита regsvr32 зарегистрирует заместитель/заглушку в реестре системы.

Еще раз напомню, что библиотека dll заместителя/заглушки должна быть скопирована и зарегистрирована и на компьютере сервера, и на компьютере клиента.

Создание клиента

Чтобы включить в клиентский проект описания интерфейсов сервера, следует импортировать библиотеку типов сервера. Для этого в файл StdAfx.h клиентского проекта нужно вставить следующую строку:

#import «DCOM_server.tlb»
 named_guids no_namespace

(подробное описание директивы #import и ее атрибутов есть в MSDN).

Укажите путь к каталогу серверного проекта на вкладке Project->Settings->C++->Preprocessor в окне Additional include directories, иначе компилятор не найдет файл библиотеки типов сервера.

Для компиляции кода требуется определить в проекте константу _WIN32_WINNT со значением 0x0400 (см. Экран 4).

Экран 4. Определение константы _WIN32_WINNT в клиентском проекте.

Обработка ошибок COM на стороне клиента. Использование try-catch блока в сочетании со смарт-указателями позволяет сосредоточить обработку ошибок СОМ в одном блоке и обеспечить автоматический вызов метода Release для COM-объекта как при успешном завершении, так и в случае ошибки. Класс ICarPtr создается в результате обработки импортированной библиотеки типов DCOM_server.tlb. При вызове деструктора класса вызывается метод Release для «сырого» указателя на интерфейс.

Если все вызовы методов СОМ-объекта успешны, то выполнение передается оператору, следующий за последним catch-блоком. Тогда объект spI класса ICarPtr разрушится при выходе из try-блока. Если какой-либо вызов возвращает ошибку, класс ICarPtr генерирует исключение типа _com_error, и управление передается в catch-блок. При этом для всех стековых объектов, созданных в пределах try-блока, вызываются деструкторы в порядке, обратном порядку создания этих объектов. Такой механизм называется «раскрутка стека» (stack unwinding).

Ну вот и все! Если сервер и клиент успешно скомпилировались и заработали, ищите ошибку в компиляторе. Шутка.

Наиболее вероятными причинами run-time ошибок могут быть неправильная регистрация сервера в системном реестре либо отсутствие библиотеки dll заместителя/заглушки (или незарегистрированная библиотека) на клиентском и/или серверном компьютере.

Проверьте, будет ли работать сервер при локальных вызовах. Для этого следует заменить второй аргумент функции CoCreateInstanceEx на CLSCTX_INPROC_SERVER и запустить клиентскую программу на серверном компьютере.

Если функция CoCreateInstanceEx при вызове удаленного сервера (второй аргумент CLSCTX_REMOTE_SERVER) возвращает ошибку «Класс не зарегистрирован» (код ошибки 0x80040154), а вызов локального сервера завершается успешно - скорее всего, не были внесены изменения в .rgs файл сервера.

Виталий Ли - разработчик ПО в компании «Киберплат.Ком». С ним можно связаться по адресу: vitaly1000@mail.ru.

Листинг 3. Код активизации сервера и вызовы методов.