Общие замечания

Автономный COM-сервер представляет собой обычное Windows-приложение (исполняемый файл в формате PE). Поскольку сервер и приложение-клиент в данном случае выполняются в различных адресных пространствах, для обмена данными между ними должен использоваться специальный механизм, называемый маршалингом (marshaling). Под обменом данными здесь понимается передача параметров методам реализуемых интерфейсов и получение от них результатов.

Существуют следующие три варианта реализации маршалинга.

1. Custom (пользовательский). При использовании этого варианта предоставляемые сервером COM-классы должны реализовывать интерфейс IMarshal.

2. Standard (стандартный), который строится на основании протокола ORPC (Object Remote Procedure Call, вызов удаленной объектной процедуры). Этот вариант применяется, как правило, при реализации удаленных серверов).

В обоих описанных случаях необходимо помимо собственно COM-сервера реализовать также DLL-библиотеку, являющуюся «переходником» (заглушкой, stub) между сервером и приложением-клиентом. По сути, эта DLL представляет собой внутрипроцессный сервер.

3. Universal (универсальный), при котором используется стандартная системная DLL-библиотека oleaut32.dll, выполняющая построение переходника «на лету» на основании предоставляемой COM-сервером библиотеки типов (type library). Для построения такой библиотеки необходимо использовать специальный файл на языке ODL (Object Definition Language) или IDL (Interface Definition Language, является расширением первого), который обрабатывается либо компилятором midl.exe, либо утилитой mktyplib.exe, входящими в состав Microsoft Visual Studio. Этот вариант наиболее удобен, поскольку не требует создания дополнительной DLL-библиотеки.

В качестве «расплаты» за удобство, использование универсального маршалинга предъявляет к серверу следующее требование: все методы реализуемых интерфейсов (за исключением, конечно, «стандартных» — в частности, IUnknown и IClassFactory) должны возвращать значение типа HRESULT и принимать в качестве параметров величины OLE-automation-совместимых типов.

К основным таким типам относятся char (для отдельных символов), BSTR (для строк), short и long (для целых чисел), float и double (для дробных). Кроме этого, совместимым типом будет считаться указатель на совместимый тип, однако, в отличие от C/C++, при передаче по указателю будет доступен только один элемент соответствующего типа, то есть передать таким образом массив невозможно — необходимо использовать специально предназначенный для этого тип SAFEARRAY, также являющийся OLE-automation-совместимым.

Кроме перечисленных, компилятор MIDL допускает также использование типа int для целочисленных параметров, но при этом их размер является системно-зависимым и может на различных платформах составлять 2, 4 или 8 байт.

Заметим, что внутрипроцессные сервера тоже имеет смысл реализовывать как OLE-automation-совместимые, поскольку это существенно облегчает обращение к ним из приложений-клиентов, создаваемых с использованием других языков программирования. Естественно, эта рекомендация не носит ультимативного характера, поскольку все определяется назначением и сферой применения конкретного COM-сервера.

Синтаксис описания библиотеки типов

Файл на ODL, описывающий библиотеку типов, должен иметь следующую структуру:

[атрибуты] library имя_библиотеки
{
   // объявления интерфейсов и COM-классов
};

Здесь атрибуты — это задаваемые с помощью ключевых слов ODL характеристики данной библиотеки типов (если задается несколько атрибутов, они должны быть перечислены через запятую); имя_библиотеки может быть произвольным (как правило, оно совпадает с именем COM-сервера). Обязательным является указание атрибута uuid, назначающего уникальный идентификатор данной библиотеки типов (library identifier, LIBID) и имеющего синтаксис

uuid(идентификатор_библиотеки)

где идентификатор должен быть указан в формате Registry Format утилиты guidgen.exe, но без фигурных скобок. Ниже перечислены остальные допустимые атрибуты.

version(основной_номер.дополнительный_номер)

Этот атрибут позволяет задать номер версии библиотеки типов.

helpstring("строка")

Указанная строка будет отображаться в качестве подсказки различными браузерами COM-компонентов (один из них доступен, например, в среде Visual Basic).

Таким образом, пример описания библиотеки типов может выглядеть так:

[uuid(72FE438D-7EF8-4D1B-A694-6295CFC24EF8), version(1.0)]
library SuperLib
{
   // объявления интерфейсов и COM-классов
};

В теле описания библиотеки (внутри фигурных скобок) может быть использована директива

importlib("имя_библиотеки_типов");

предоставляющая доступ с объявлениям, сделанным в библиотеке типов с заданным именем. Это бывает необходимо при обращении к типам и интерфейсам, реализуемым другим COM-сервером. Например, почти каждая библиотека типов содержит директиву

importlib("stdole32.tlb");

позволяющую использовать «стандартные» интерфейсы OLE/COM. Заметим, что по своему назначению директива ODL importlib аналогична директиве препроцессора #include в языках C/C++.

Объявления интерфейсов должны содержаться в теле описания библиотеки типов и иметь следующий вид:

[атрибуты] interface имя_интерфейса: имя_предка
{
   // объявления методов интерфейса
};

Обязательными атрибутами являются object, говорящий о том, что данный элемент относится к COM, и uuid, назначающий уникальный идентификатор объявляемого интерфейса. При использовании универсального маршалинга обязательным является также атрибут oleautomation.

Помимо перечисленных, при объявлении интерфейса могут использоваться атрибуты version и helpstring, синтаксис и назначение которых аналогичны одноименным атрибутам самой библиотеки типов.

Объявления методов интерфейса должны иметь вид

тип_возвр_знач имя_метода (
   [атрибут_парам_1] тип_парам_1 имя_парам_1,
   [атрибут_парам_2] тип_парам_2 имя_парам_2,
   ...
);

В качестве атрибута параметра указывается ключевое слово in для входных или out — для выходных параметров. Заметим, что выходные параметры должны быть указателями и их может быть несколько (а может и не быть совсем). Если метод не принимает параметров, то вместо их списка необходимо, как и в C/C++, использовать ключевое слово void.

Объявления COM-классов должны содержаться в теле описания библиотеки типов и включать в себя имена всех реализуемых данным классом интерфейсов. Они оформляются следующим образом:

[атрибуты] coclass имя_класса
{
   interface имя_интерфейса_1;
   interface имя_интерфейса_2;
   ...
};

Обязательным является атрибут uuid, назначающий уникальный идентификатор COM-класса; кроме него допустимы атрибуты version и helpstring.

Ниже приведен законченный пример файла на ODL, описывающего библиотеку типов гипотетического COM-сервера.

[uuid(72FE438D-7EF8-4D1B-A694-6295CFC24EF8), version(1.0)]
library SuperLib
{
   // включаемая библиотека типов
   importlib("stdole32.tlb");

   // реализуемый интерфейс
   [object, uuid(E9C494F5-6109-4518-9C3C-02ED3921E665), oleautomation]
   interface IWorker: IUnknown
   {
      HRESULT DoSomethig([in] BSTR bstrParam);
      HRESULT UndoSomething(void);
      HRESULT GetSomething([in] long nParam, [out] double* pDest);
   };

   // предоставляемый COM-класс
   [uuid(84FA653D-CBCF-41A9-8E87-689C01EE3E6D), version(1.0)]
   coclass CoMyClass
   {
      interface IWorker;
   };
};

Заметим, что именно библиотека типов, полученная в результате компиляции файла, написанного на ODL, является унифицированным источником информации об интерфейсах и классах, предоставляемых соответствующим COM-сервером, независимо от того, каков его тип — внутрипроцессный или автономный. Библиотеки типов распознаются любым «COM-совместимым» средством разработки, к числу которых относятся Delphi, C++Builder и Visual Basic; в силу этого, можно рекомендовать их создание не только для автономных, но и для внутрипроцессных COM-серверов, если предполагается написание приложений-клиентов на языке, отличном от C/C++.

Использование типа данных SAFEARRAY

Заголовочный файл #include <oleauto.h>
Библиотека импорта oleaut32.lib

Тип SAFEARRAY, формально являющийся структурой, объявлен в заголовочном файле <oleauto.h>; при описании соответствующего параметра интерфейса в .odl файле используется конструкция вида

SAFEARRAY(тип_элементов) имя_параметра

причем для использования универсального маршалинга указанный тип_элементов должен быть OLE-automation-совместимым. В тексте на C/C++ такой параметр должен иметь тип SAFEARRAY*. Ниже приведены основные функции, предназначенные для работы с «безопасными массивами».

Создание массива выполняется с помощью функции

SAFEARRAY* SafeArrayCreate(
   VARTYPE vt,
   unsigned int uNumDims,
   SAFEARRAYBOUND* psab
);

Значение параметра vt определяется требуемым типом элементов массива:

тип элементов vt
char VT_I1
unsigned char VT_UI1
BSTR VT_BSTR
short VT_I2
long VT_I4
float VT_R4
double VT_R8

Параметр uNumDims задает количество измерений создаваемого массива, а через параметр psab передается адрес массива структур, содержащих информацию о границах каждого измерения. Структура SAFEARRAYBOUND имеет следующие поля:

ULONG cElements
количество элементов данного измерения;
LONG lLbound
индекс первого элемента в данном измерении (заметим, что это значение может быть отрицательным).

При успешном выполнении функция возвращает указатель, идентифицирующий созданный массив, а в случае ошибки — значение NULL. Таким образом, для создания «таблицы» целых чисел, состоящей из 4-х строк и 3-х столбцов, необходимо выполнить следующие действия:

SAFEARRAYBOUND asab[] = { {4, 0}, {3, 0} };
SAFEARRAY* psaTable = ::SafeArrayCreate(VT_I4, 2, asab);
if (psaTable != NULL)
{
   // все получилось
}

Для создания одномерного массива можно воспользоваться функцией

SAFEARRAY* SafeArrayCreateVector(
   VARTYPE vt,
   long lLbound,
   unsigned int cElements
);

создающей вектор элементов типа vt с индексом первого элемента равным lLbound и количеством элементов cElements. При успешном выполнении функция возвращает указатель, идентифицирующий созданный массив, а в случае ошибки — значение NULL.

Уничтожение массива производится с помощью функции

HRESULT SafeArrayDestroy(
   SAFEARRAY* psa
);

которая освобождает память, занятую массивом psa, и возвращает одно из следующих значений:

S_OK
массив успешно уничтожен;
DISP_E_ARRAYISLOCKED
массив блокирован в памяти вызовом функции SafeArrayLock и его уничтожение в данный момент невозможно;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива.

Запись элемента в массив выполняется с помощью функции

HRESULT SafeArrayPutElement(
   SAFEARRAY* psa,
   long* pnIndices,
   void* pvSrc
);

Параметр psa должен идентифицировать массив, а через параметр pnIndices необходимо передать адрес массива, содержащего все «координаты» элемента, в который будет скопировано значение переменной, находящейся по адресу pvSrc. Заметим, что в случае использования типа BSTR, который уже является указателем, соответствующую переменную необходимо передавать по значению, без дополнительной операции взятия адреса:

SAFEARRAY* psaNumbers = ::SafeArrayCreateVector(VT_I4, 0, 10);
long anNumIndices[] = { 3 };
long nValue = 1972;

// берем адрес
::SafeArrayPutElement(psaNumbers, anNumIndices, &nValue);
...

SAFEARRAY* psaStrings = ::SafeArrayCreateVector(VT_BSTR, 0, 5);
long anStrIndices[] = { 1 };
BSTR bstrValue = ::SysAllocString(OLESTR("Hello!"));

// передаем как есть
::SafeArrayPutElement(psaStrings, anStrIndices, bstrValue);

Функция SafeArrayPutElement может вернуть одно из следущих значений:

S_OK
элемент успешно записан;
DISP_E_BADINDEX
в массиве pnIndices содержится недопустимая «координата»;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива;
E_OUTOFMEMORY
не хватило памяти для записи элемента.

Чтение элемента массива осуществляется с помощью функции

HRESULT SafeArrayGetElement(
   SAFEARRAY* psa,
   long* pnIndices,
   void* pvDest
);

Параметр psa должен идентифицировать массив, а через параметр pnIndices необходимо передать адрес массива, содержащего все «координаты» элемента, значение которого должно быть скопировано в переменную по адресу pvDest. Функция может вернуть одно из следующих значений:

S_OK
элемент успешно прочитан;
DISP_E_BADINDEX
в массиве pnIndices содержится недопустимая «координата»;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива;
E_OUTOFMEMORY
не хватило памяти для чтения элемента.

Получение границ массива. Существует функция

HRESULT SafeArrayGetLBound(
   SAFEARRAY* psa,
   unsigned int uDim,
   long* pnDest
);

которая записывает в переменную по адресу pnDest индекс первого элемента для измерения с индексом uDim (причем индекс первого измерения равен 1) в массиве psa и возвращает одно из следующих значений:

S_OK
успешное выполнение;
DISP_E_BADINDEX
через параметр uDim передан недопустимый индекс;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива.

Получить индекс последнего элемента для заданного измерения можно с помощью функции

HRESULT SafeArrayGetUBound(
   SAFEARRAY* psa,
   unsigned int uDim,
   long* pnDest
);

параметры и возможные возвращаемые значения которой такие же, как и у функции SafeArrayGetLBound.

Копирование массива может быть выполнено с помощью одной из следующих функций.

HRESULT SafeArrayCopy(
   SAFEARRAY* psaSrc,
   SAFEARRAY** ppsaDest
);

Эта функция создает массив, являющийся копией psaSrc, и записывает его идентификатор в переменную по адресу ppsaDest. Созданный массив должен быть впоследствии удален с помощью функции SafeArrayDestroy. В свою очередь, функция

HRESULT SafeArrayCopyData(
   SAFEARRAY* psaSrc,
   SAFEARRAY* psaDest
);

копирует данные из массива psaSrc на место данных массива psaDest. Обе эти функции могут возвращать одно из следующих значений:

S_OK
успешное выполнение;
E_INVALIDARG
задан некорректный «идентификатор» массива;
E_OUTOFMEMORY
не хватило памяти для копирования данных.

Получение типа элементов массива выполняется с помощью функции

HRESULT SafeArrayGetVartype(
   SAFEARRAY* psa,
   VARTYPE* pvtDest
);

которая записывает тип элементов массива psa в переменную по адресу pvtDest и возвращает одно из следующих значений:

S_OK
успешное выполнение;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива.

Заметим, что для корректной работы этой функции необходимо сразу же после создания массива установить для него флаг FADF_HAVEVARTYPE. Это делается следующим образом:

SAFEARRAY* psa = ::SafeArrayCreateVector(VT_R8, 0, 10);
if (psa != NULL)
{
   psa->fFeatures |= FADF_HAVEVARTYPE;
}

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

HRESULT SafeArrayLock(
   SAFEARRAY* psa
);

возвращающей одно из следующих значений:

S_OK
массив успешно блокирован;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива;
E_UNEXPECTED
массив не может быть блокирован.

В случае успешного блокирования массива, в поле pvData структуры SAFEARRAY, адресуемой параметром psa, будет записан адрес области памяти, в которой располагаются элементы массива. Полученный таким образом указатель можно использовать, например, для «группового» чтения/записи элементов с помощью функции memmove.

// создаем массив для хранения 3-х целых чисел
SAFEARRAY* psa = ::SafeArrayCreateVector(VT_I4, 0, 3);

// блокируем созданный массив
::SafeArrayLock(psa);

// копируем в блокированный массив данные
long anData[] = { 3, 2, 1972 };
memmove(psa->pvData, anData, sizeof(anData));

Разблокирование массива выполняется с помощью функции

HRESULT SafeArrayUnlock(
   SAFEARRAY* psa
);

которая может вернуть одно из следующих значений:

S_OK
массив успешно разблокирован;
E_INVALIDARG
значение, переданное через параметр psa, не является «идентификатором» массива;
E_UNEXPECTED
массив не может быть разблокирован.

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

Информация в системном реестре

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

1. Должен существовать ключ вида

HKEY_CLASSES_ROOT\CLSID\{идентификатор_класса}

содержащий в качестве значения по умолчанию (безымянного значения) строку вида

программный_идентификатор

2. Должен существовать ключ вида

HKEY_CLASSES_ROOT\CLSID\{идентификатор_класса}\LocalServer32

содержащий в качестве значения по умолчанию строку с полным именем исполняемого файла сервера.

3. Должен существовать ключ вида

HKEY_CLASSES_ROOT\программный_идентификатор\CLSID

содержащий в качестве значения по умолчанию строку вида

{идентификатор_класса}

Нетрудно видеть, что единственное отличие от внутрипроцессного сервера состоит в имени второго ключа: для записи имени исполняемого файла сервера следует использовать ключ LocalServer32 вместо InprocServer32.

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

HRESULT LoadTypeLibEx(
   LPCOLESTR pszName,
   REGKIND regKind,
   ITypeLib** ppTypeLib
);

Через параметр pszName передается полное имя библиотеки типов. В качестве параметра regKind можно указать одно из следующих значений:

REGKIND_DEFAULT
загрузить библиотеку типов и выполнить ее регистрацию «по правилам» функции LoadTypeLib;
REGKIND_REGISTER
загрузить библиотеку типов и вызвать функцию RegisterTypeLib для ее регистрации;
REGKIND_NONE
загрузить библиотеку типов, не выполняя ее регистрации.

Функция записывает полученный указатель на интерфейс ITypeLib в переменную по адресу ppTypeLib и возвращает одно из следующих значений:

S_OK
успешное выполнение;
E_OUTOFMEMORY
для загрузки библиотеки типов недостаточно памяти;
E_INVALIDARG
функции передан недопустимый аргумент;
TYPE_E_IOERROR
невозможно выполнить запись в файл;
TYPE_E_REGISTRYACCESS
нет доступа к системному реестру;
TYPE_E_INVALIDSTATE
невозможно открыть заданную библиотеку типов;
TYPE_E_CANTLOADLIBRARY
невозможно загрузить библиотеку типов.

После загрузки библиотеки типов для ее регистрации используется функция

HRESULT RegisterTypeLib(
   ITypeLib* pTypeLib,
   OLECHAR* pszName,
   OLECHAR* pszHelpDir
);

которой передается полученный ранее указатель на интерфейс ITypeLib pTypeLib, полное имя библиотеки типов pszName и имя папки pszHelpDir, в которой находится справочный файл для регистрируемой библиотеки (если он отсутствует, данный параметр следует задать равным NULL). Эта функция может вернуть одно из следующих значений:

S_OK
успешное выполнение;
E_OUTOFMEMORY
для регистрации библиотеки типов недостаточно памяти;
E_INVALIDARG
функции передан недопустимый аргумент;
TYPE_E_IOERROR
невозможно выполнить запись в файл;
TYPE_E_REGISTRYACCESS
нет доступа к системному реестру;
TYPE_E_INVALIDSTATE
невозможно открыть заданную библиотеку типов.

Заметим, что, несмотря на возможность регистрации библиотеки типов при ее загрузке (посредством задания флагов REGKIND_REGISTER или REGKIND_DEFAULT), официальная документация рекомендует выполнять эти операции раздельно:

ITypeLib* pLib;

// загружаем библиотеку типов
::LoadTypeLibEx(OLESTR("C:\\MyServer\\SuperLib.tlb"), REGKIND_NONE, &pLib);

// регистрируем ее
::RegisterTypeLib(pLib, OLESTR("C:\\MyServer\\SuperLib.tlb"), NULL);

// отпускаем интерфейс
pLib->Release();

При деинсталляции сервера необходимо удалить информацию о библиотеке типов из системного реестра с помощью функции

HRESULT UnRegisterTypeLib(
   REFGUID libid,
   unsigned short wVerMajor,
   unsigned short wVerMinor,
   LCID idLocale,
   SYSKIND sysKind
);

Через параметр libid передается идентификатор библиотеки, через параметры wVerMajor и wVerMinor — номер ее версии. Параметр idLocale задает так называемый идентификатор территории (locale identifier); как показывает опыт, наименее «рискованным» является значение

MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), SORT_DEFAULT)

И наконец через параметр sysKind передается идентификатор платформы; под Win32 его необходимо задавать равным SYS_WIN32. Функция возвращает те же значения, что и RegisterTypeLib.

Откомпилированная библиотека типов может быть включена в ресурсы исполняемого файла сервера; в этом случае мы избавляемся от необходимости распространения вместе с ним отдельного .tlb файла. Для этого в среде Microsoft Visual C++ необходимо выбрать команду Resource… меню Insert и в появившемся диалоговом окне Insert Resource нажать на кнопочку Import…; откроется диалоговое окно Import Resource, где необходимо в поле ввода File name указать имя .tlb файла, в списке Open as выбрать пункт Custom и нажать на кнопочку Import; в появившемся после этого диалоговом окне Custom Resource Type нужно ввести строку «TYPELIB» (без кавычек) в поле ввода Resource type и нажать кнопку OK. После этого, в свойствах добавленного ресурса необходимо назначить ему идентификатор, равный 1. При «ручном» редактировании ресурсов процедура существенно упрощается — достаточно вписать в .rc файл строку вида

1 TYPELIB "имя_файла.tlb"

При загрузке и регистрации библиотеки типов, находящейся в ресурсах исполняемого файла сервера, в качестве ее имени указывается имя файла самого сервера; если значение идентификатора отличается от 1, то его также необходимо указывать в имени сервера, добавляя дополнительный символ «\»:

::LoadTypeLibEx(OLESTR("C:\\MyServer\\SuperSrv.exe\\38"), ...);
::RegisterTypeLib(..., OLESTR("C:\\MyServer\\SuperSrv.exe\\38"), ...);

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

Особенности реализации фабрики класса

В автономном COM-сервере фабрика каждого класса должна создаваться в единственном экземпляре в начале его работы (в функции WinMain). После создания фабрики ее необходимо зарегистрировать в COM-библиотеке вызовом функции

STDAPI CoRegisterClassObject(
   REFCLSID clsid,
   IUnknown* pUnk,
   DWORD fdwContext,
   DWORD fdwFlags,
   DWORD* pdwFactID
);

Через параметр clsid ей передается идентификатор COM-класса, для создания экземпляров которого предназначена регистрируемая фабрика. Параметр pUnk должен содержать указатель на интерфейс IClassFactory регистрируемой фабрики. Параметр fdwContext используется для указания типа COM-сервера и формируется как комбинация флагов CLSCTX_xxx; заметим, что при указании флага CLSCTX_LOCAL_SERVER COM-библиотека автоматически добавляет флаг CLSCTX_INPROC_SERVER, позволяющий создавать COM-объекты не только приложениям-клиентам, но и самому серверу. Параметр fdwFlags определяет порядок использования сервера и может быть комбинацией следующих флагов:

REGCLS_SINGLEUSE
только приложение-клиент, инициировавшее загрузку сервера, может создавать экземпляры предоставляемых этим сервером COM-классов;
REGCLS_MULTIPLEUSE
«услугами» сервера может пользоваться любое количество клиентов, при этом сервер будет загружен в память в единственном экземпляре;
REGCLS_SUSPENDED
сервер будет загружен в память, но для регистрации им фабрик классов и получения возможности создавать COM-объекты необходимо будет вызвать функцию CoResumeClassObjects.

Естественно, первые два флага являются взаимоисключающими. Функция записывает в переменную по адресу pdwFactID числовой идентификатор зарегистрированной фабрики класса, который используется для отмены регистрации, и возвращает одно из следующих значений:

S_OK
успешное выполнение;
CO_E_OBJISREG
фабрика класса уже зарегистрирована;
E_INVALIDARG
один из аргументов имеет недопустимое значение;
E_OUTOFMEMORY
не хватило памяти для регистрации фабрики.

Перед завершением работы сервера необходимо отменить регистрацию фабрики класса в COM-библиотеке с помощью функции

HRESULT CoRevokeClassObject(
   DWORD dwFactID
);

передав ей через параметр dwFactID идентификатор фабрики, полученный при вызове CoRegisterClassObject. Данная функция возвращает одно из следующих значений:

S_OK
регистрация фабрики успешно отменена;
E_INVALIDARG
один из аргументов имеет недопустимое значение;
E_OUTOFMEMORY
не хватило памяти для отмены регистрации.

Процесс разработки

В качестве примера мы рассмотрим разработку простейшего автономного сервера, предоставляющего клиентам COM-класс CoFile, останавливаясь на отличиях реализации этого сервера от его внутрипроцессного «предшественника», процесс разработки которого описан в заключительной части предыдущей темы — Создание внутрипроцессного COM-сервера. Напомню, что задачей класса CoFile является инкапсуляция основных операции файлового ввода/вывода, которые разделены на три группы (инициализация, чтение/запись данных, получение информации), образующие соответственно интерфейсы IFileInit, IFileOperate и IFileStatus.

Шаг 0: объявление и реализация вспомогательного класса, инкапсулирующего операции с «безопасными массивами».

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// SafeArray.hpp

// объявление

template <class T_ELEM>
class CSafeArray
{
public:
   CSafeArray(void);
   ~CSafeArray(void);
public:
   BOOL Create(UINT cDims, SAFEARRAYBOUND* psab);
   BOOL CreateVector(long nLbound, UINT cElements);
   BOOL Destroy(void);
   BOOL Attach(SAFEARRAY* psa);
   SAFEARRAY* Detach(void);
public:
   operator SAFEARRAY*(void);
public:
   void* Lock(void);
   BOOL Unlock(void);
public:
   BOOL PutElement(long* pnIndices, T_ELEM src);
   BOOL PutElement(long nIndex, T_ELEM src);
   BOOL GetElement(long* pnIndices, T_ELEM* dest);
   T_ELEM GetElement(long nIndex);
public:
   BOOL PutData(const void* pvSrc, UINT uSize);
   BOOL GetData(void* pvDest, UINT uSize);
public:
   UINT GetDim(void);
   BOOL GetLBound(UINT uDim, long* pnDest);
   long GetLBound(UINT uDim);
   BOOL GetUBound(UINT uDim, long* pnDest);
   long GetUBound(UINT uDim);
   BOOL GetVartype(VARTYPE* pvtDest);
   VARTYPE GetVartype(void);
private:
   SAFEARRAY* m_psa;
};

// реализация

template <class T_ELEM>
inline CSafeArray<T_ELEM>::CSafeArray(void):
m_psa(NULL)
{
}

template <class T_ELEM>
inline CSafeArray<T_ELEM>::~CSafeArray(void)
{
   Destroy();
}

template <>
BOOL CSafeArray<char>::Create(UINT cDims, SAFEARRAYBOUND* psab)
{
   if (m_psa == NULL)
   {
      m_psa = ::SafeArrayCreate(VT_I1, cDims, psab);
      if (m_psa != NULL)
      {
         m_psa->fFeatures |= FADF_HAVEVARTYPE;
         return (TRUE);
      }
   }
   return (FALSE);
}

template <>
BOOL CSafeArray<BSTR>::Create(UINT cDims, SAFEARRAYBOUND* psab)
{
   if (m_psa == NULL)
   {
      m_psa = ::SafeArrayCreate(VT_BSTR, cDims, psab);
      if (m_psa != NULL)
      {
         m_psa->fFeatures |= FADF_HAVEVARTYPE;
         return (TRUE);
      }
   }
   return (FALSE);
}

template <>
BOOL CSafeArray<char>::CreateVector(long nLbound, UINT cElements)
{
   if (m_psa == NULL)
   {
      m_psa = ::SafeArrayCreateVector(VT_I1, nLbound, cElements);
      if (m_psa != NULL)
      {
         m_psa->fFeatures |= FADF_HAVEVARTYPE;
         return (TRUE);
      }
   }
   return (FALSE);
}

template <>
BOOL CSafeArray<BSTR>::CreateVector(long nLbound, UINT cElements)
{
   if (m_psa == NULL)
   {
      m_psa = ::SafeArrayCreateVector(VT_BSTR, nLbound, cElements);
      if (m_psa != NULL)
      {
         m_psa->fFeatures |= FADF_HAVEVARTYPE;
         return (TRUE);
      }
   }
   return (FALSE);
}

template <class T_ELEM>
BOOL CSafeArray<T_ELEM>::Destroy(void)
{
   if (m_psa != NULL)
   {
      while (m_psa->cLocks > 0)
      {
         ::SafeArrayUnlock(m_psa);
      }
      BOOL fSuccess = SUCCEEDED(::SafeArrayDestroy(m_psa));
      m_psa = NULL;
      return (fSuccess);
   }
   else
   {
      return (FALSE);
   }
}

template <class T_ELEM>
BOOL CSafeArray<T_ELEM>::Attach(SAFEARRAY* psa)
{
   if (m_psa == NULL)
   {
      m_psa = psa;
      return (TRUE);
   }
   else
   {
      return (FALSE);
   }
}

template <class T_ELEM>
SAFEARRAY* CSafeArray<T_ELEM>::Detach(void)
{
   SAFEARRAY* psaTemp = m_psa;
   m_psa = NULL;
   return (psaTemp);
}

template <class T_ELEM>
inline CSafeArray<T_ELEM>::operator SAFEARRAY*(void)
{
   return (m_psa);
}

template <class T_ELEM>
void* CSafeArray<T_ELEM>::Lock(void)
{
   return (SUCCEEDED(::SafeArrayLock(m_psa)) ? m_psa->pvData : NULL);
}

template <class T_ELEM>
BOOL CSafeArray<T_ELEM>::Unlock(void)
{
   return (SUCCEEDED(::SafeArrayUnlock(m_psa)));
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::PutElement(long* pnIndices, T_ELEM src)
{
   return (SUCCEEDED(::SafeArrayPutElement(m_psa, pnIndices, &src)));
}

template <>
inline BOOL CSafeArray<BSTR>::PutElement(long* pnIndices, BSTR bstrSrc)
{
   return (SUCCEEDED(::SafeArrayPutElement(m_psa, pnIndices, bstrSrc)));
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::PutElement(long nIndex, T_ELEM src)
{
   return (SUCCEEDED(::SafeArrayPutElement(m_psa, &nIndex, &src)));
}

template <>
inline BOOL CSafeArray<BSTR>::PutElement(long nIndex, BSTR bstrSrc)
{
   return (SUCCEEDED(::SafeArrayPutElement(m_psa, &nIndex, bstrSrc)));
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::GetElement(long* pnIndices, T_ELEM* pDest)
{
   return (SUCCEEDED(::SafeArrayGetElement(m_psa, pnIndices, pDest)));
}

template <class T_ELEM>
inline T_ELEM CSafeArray<T_ELEM>::GetElement(long nIndex)
{
   T_ELEM temp;

   ::SafeArrayGetElement(m_psa, &nIndex, &temp);
   return (temp);
}

template <class T_ELEM>
BOOL CSafeArray<T_ELEM>::PutData(const void* pvSrc, UINT uSize)
{
   if (m_psa != NULL)
   {
      void* pvDest = Lock();
      if (pvDest != NULL)
      {
         memmove(pvDest, pvSrc, uSize);
         Unlock();
         return (TRUE);
      }
   }
   return (FALSE);
}

template <class T_ELEM>
BOOL CSafeArray<T_ELEM>::GetData(void* pvDest, UINT uSize)
{
   if (m_psa != NULL)
   {
      void* pvSrc = Lock();
      if (pvSrc != NULL)
      {
         memmove(pvDest, pvSrc, uSize);
         Unlock();
         return (TRUE);
      }
   }
   return (FALSE);
}

template <class T_ELEM>
inline UINT CSafeArray<T_ELEM>::GetDim(void)
{
   return (::SafeArrayGetDim(m_psa));
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::GetLBound(UINT uDim, long* pnDest)
{
   return (SUCCEEDED(::SafeArrayGetLBound(m_psa, uDim, pnDest)));
}

template <class T_ELEM>
inline long CSafeArray<T_ELEM>::GetLBound(UINT uDim)
{
   long nBound;

   ::SafeArrayGetLBound(m_psa, uDim, &nBound);
   return (nBound);
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::GetUBound(UINT uDim, long* pnDest)
{
   return (SUCCEEDED(::SafeArrayGetUBound(m_psa, uDim, pnDest)));
}

template <class T_ELEM>
inline long CSafeArray<T_ELEM>::GetUBound(UINT uDim)
{
   long nBound;

   ::SafeArrayGetUBound(m_psa, uDim, &nBound);
   return (nBound);
}

template <class T_ELEM>
inline BOOL CSafeArray<T_ELEM>::GetVartype(VARTYPE* pvtDest)
{
   return (SUCCEEDED(::SafeArrayGetVartype(m_psa, pvtDest)));
}

template <class T_ELEM>
inline VARTYPE CSafeArray<T_ELEM>::GetVartype(void)
{
   VARTYPE vt;

   ::SafeArrayGetVartype(m_psa, &vt);
   return (vt);
}

Конструктор класса CSafeArray (строки 44…48) обнуляет идентификатор связанного с объектом «безопасного массива», хранящийся в поле m_psa; деструктор (строки 50…54) вызывает метод Destroy для уничтожения соответствующего массива.

Методы Create (строки 56…84) и CreateVector (строки 86…114), предназначенные для создания соответственно произвольного и одномерного массива, оформлены как специализации для поддерживаемых классом типов данных — char и BSTR. Аналогичным образом можно реализовать специализированные варианты для остальных OLE-automation-совместимых типов. Метод Destroy (строки 116…133) вначале разблокирует массив, если это необходимо (строки 121…124), после чего уничтожает его (строка 125) и обнуляет идентификатор (строка 126).

Методы Attach (строки 135…147) и Detach (строки 149…155) позволяют соответственно связывать экземпляр класса CSafeArray с уже существующим «безопасным массивом» и разрывать эту связь. Пример их использования рассматривается на шаге 4.

Метод PutElement (строки 175…197) предназначен для записи в массив элемента; отметим что для типа данных BSTR реализован специализированный вариант (строки 181…185), поскольку в данном случае не требуется взятие адреса записываемого элемента. Определены также перегруженные варианты этого метода для записи элемента в одномерный массив (строки 187…197). Метод GetElement позволяет получить значение элемента как в массиве с произвольным количеством измерений (строки 199…203), так и в одномерном (строки 205…212).

Методы PutData (строки 214…228) и GetData (строки 230…244) предназначены соответственно для записи и чтения данных в область памяти, где находятся элементы массива.

Оставшиеся методы носят информационный характер. Метод GetDim (строки 246…250) позволяет узнать количество измерений массива, а методы GetLBound (строки 252…265) и GetUBound (строки 267…280) — соответственно нижнюю и верхнюю границы заданного измерения. Метод GetVartype (строки 282…295) возвращает идентификатор типа элементов массива.

Шаг 1: генерация идентификаторов и связывание их с предоставляемыми интерфейсами.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Identifiers.h

#define S_UUID_IFileInit "FCBD8815-7A29-40BA-BB57-9C376D458E3D"
#define S_UUID_IFileOperate "61F6072B-89FE-454A-9106-302FDD301F3D"
#define S_UUID_IFileStatus "0E24DEA2-77E0-4508-82E3-14AB82F1474A"
#define S_UUID_CoFile "A67C5446-AE86-45A6-A47A-F365082F7A3D"
#define S_UUID_FileServer "6937874D-7CAF-4971-BDE8-EFED07029CC8"

#if !defined(__MKTYPLIB__) && !defined(__midl)

struct __declspec(uuid(S_UUID_IFileInit)) IFileInit;
#define IID_IFileInit __uuidof(IFileInit)
struct __declspec(uuid(S_UUID_IFileOperate)) IFileOperate;
#define IID_IFileOperate __uuidof(IFileOperate)
struct __declspec(uuid(S_UUID_IFileStatus)) IFileStatus;
#define IID_IFileStatus __uuidof(IFileStatus)

class __declspec(uuid(S_UUID_CoFile)) CoFile;
#define CLSID_CoFile __uuidof(CoFile)

struct __declspec(uuid(S_UUID_FileServer)) FileServer;
#define LIBID_FileServer __uuidof(FileServer)

#endif	// __MKTYPLIB__ && __midl

Этот файл является практически точной копией своего «тезки», использовавшегося при разработке внутрипроцессного COM-сервера. Добавился уникальный идентификатор библиотеки типов (строки 7 и 21…22); директивы условной компиляции (строки 9 и 24) позволяют включать данный заголовок в исходные файлы, написанные как на C++, так и на ODL/IDL.

Шаг 2: объявление предоставляемых интерфейсов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Interfaces.h

struct IFileInit: public IUnknown
{
   STDMETHOD(Open)(BSTR bstrName, BSTR bstrMode) PURE;
   STDMETHOD(Close)(void) PURE;
};

struct IFileOperate: public IUnknown
{
   STDMETHOD(Read)(SAFEARRAY* psaDest, long nSize) PURE;
   STDMETHOD(Write)(SAFEARRAY* psaSrc, long nSize) PURE;
   STDMETHOD(Seek)(long nOffset, long nOrigin) PURE;
   STDMETHOD(ReadString)(BSTR* pbstrDest, long nMaxLen) PURE;
   STDMETHOD(WriteString)(BSTR bstrSrc) PURE;
};

struct IFileStatus: public IUnknown
{
   STDMETHOD(Tell)(long* pnDest) PURE;
};

Основное отличие объявленных интерфейсов от их предыдущего варианта заключается в том, что все они являются OLE-automation-совместимыми, обеспечивая, тем самым, возможность использования универсального маршалинга.

Второе, на что следует обратить внимание — это применение макросов STDMETHOD и PURE при объявлении методов интерфейсов (строки 5…6, 11…15 и 20). Оба этих макроса определены в заголовочных файлах <basetyps.h> и <objbase.h> и предназначены для повышения переносимости COM-программ на уровне исходного текста и некоторого сокращения объема этого текста. Макрос STDMETHOD имеет «прототип»

STDMETHOD(имя_метода)

и после обработки препроцессором C++ раскрывается в

virtual HRESULT __stdcall имя_метода

а макрос PURE заменяется препроцессором на строку «= 0», делающую объявляемый метод чисто-виртуальным.

Шаг 3: объявление класса, реализующего предоставляемые интерфейсы.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// CoFileClass.h

class CoFile: IFileInit, IFileOperate, IFileStatus
{
   friend class CoFileFactory;
public:
   CoFile(void);
   ~CoFile(void);
public:
   STDMETHOD(QueryInterface)(REFIID iid, void** ppvDest);
   STDMETHOD_(ULONG, AddRef)(void);
   STDMETHOD_(ULONG, Release)(void);
public:
   STDMETHOD(Open)(BSTR bstrName, BSTR bstrMode);
   STDMETHOD(Close)(void);
public:
   STDMETHOD(Read)(SAFEARRAY* psaDest, long nSize);
   STDMETHOD(Write)(SAFEARRAY* psaSrc, long nSize);
   STDMETHOD(Seek)(long nOffset, long nOrigin);
   STDMETHOD(ReadString)(BSTR* pbstrDest, long nMaxLen);
   STDMETHOD(WriteString)(BSTR bstrSrc);
public:
   STDMETHOD(Tell)(long* pnDest);
private:
   int m_nNumRefs;
   FILE* m_fp;
   static int m_nNumObjs;
};

Как и в предыдущем, «внутрипроцессном» варианте, используется множественное наследование COM-класса от всех реализуемых им интерфейсов (строка 3); класс CoFileFactory объявлен дружественным (строка 5) для предоставления ему доступа к закрытому полю m_nNumObjs (строка 27), в котором хранится количество созданных сервером экземпляров класса CoFile.

Отметим, что в объявлениях методов класса, помимо STDMETHOD, используется еще один «стандартный» макрос COM, определение которого также содержится в заголовочных файлах <basetyps.h> и <objbase.h>. Макрос STDMETHOD_ (строки 11…12) имеет «прототип»

STDMETHOD_(тип_возвр_знач, имя_метода)

и предназначен для объявления методов, тип возвращаемого значения которых отличается от HRESULT; после обработки препроцессором C++ он раскрывается в

virtual тип_возвр_знач __stdcall имя_метода

Шаг 4: реализация методов COM-класса.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// CoFileClass.cpp

CoFile::CoFile(void)
{
   m_nNumRefs = 0;
   m_fp = NULL;
   ++m_nNumObjs;
}

CoFile::~CoFile(void)
{
   if (--m_nNumObjs == 0 && CoFileFactory::m_nNumLocks == 0)
   {
      ::PostQuitMessage(0);
   }
}

STDMETHODIMP CoFile::QueryInterface(REFIID iid, void** ppvDest)
{
   if (iid == IID_IUnknown)
   {
      *ppvDest = static_cast<IUnknown*>(static_cast<IFileInit*>(this));
   }
   else if (iid == IID_IFileInit)
   {
      *ppvDest = static_cast<IFileInit*>(this);
   }
   else if (iid == IID_IFileOperate)
   {
      *ppvDest = static_cast<IFileOperate*>(this);
   }
   else if (iid == IID_IFileStatus)
   {
      *ppvDest = static_cast<IFileStatus*>(this);
   }
   else
   {
      *ppvDest = NULL;
      return (E_NOINTERFACE);
   }
   reinterpret_cast<IUnknown*>(*ppvDest)->AddRef();
   return (S_OK);
}

STDMETHODIMP_(ULONG) CoFile::AddRef(void)
{
   return (++m_nNumRefs);
}

STDMETHODIMP_(ULONG) CoFile::Release(void)
{
   if (--m_nNumRefs == 0)
   {
      delete this;
   }
   return (m_nNumRefs);
}

STDMETHODIMP CoFile::Open(BSTR bstrName, BSTR bstrMode)
{
#if defined(_UNICODE)
   m_fp = _wfopen(bstrName, bstrMode);
#else
   char szName[_MAX_PATH];
   char szMode[16];

   wcstombs(szName, bstrName, sizeof(szName));
   wcstombs(szMode, bstrMode, sizeof(szMode));
   m_fp = fopen(szName, szMode);
#endif   // _UNICODE
   return (m_fp != NULL ? S_OK : E_FAIL);
}

STDMETHODIMP CoFile::Close(void)
{
   return (m_fp != NULL && fclose(m_fp) == 0 ? S_OK : E_FAIL);
}

STDMETHODIMP CoFile::Read(SAFEARRAY* psaDest, long nSize)
{
   if (m_fp != NULL)
   {
      CSafeArray<char> arrChars;
      arrChars.Attach(psaDest);
      long nCount = arrChars.GetUBound(1) - arrChars.GetLBound(1) + 1;
      void* pvDest = arrChars.Lock();
      fread(pvDest, 1, min(nSize, nCount), m_fp);
      arrChars.Unlock();
      arrChars.Detach();
      return (S_OK);
   }
   else
   {
      return (E_FAIL);
   }
}

STDMETHODIMP CoFile::Write(SAFEARRAY* psaSrc, long nSize)
{
   if (m_fp != NULL)
   {
      CSafeArray<char> arrChars;
      arrChars.Attach(psaSrc);
      long nCount = arrChars.GetUBound(1) - arrChars.GetLBound(1) + 1;
      void* pvSrc = arrChars.Lock();
      fwrite(pvSrc, 1, min(nSize, nCount), m_fp);
      arrChars.Unlock();
      arrChars.Detach();
      return (S_OK);
   }
   else
   {
      return (E_FAIL);
   }
}

STDMETHODIMP CoFile::Seek(long nOffset, long nOrigin)
{
   return (m_fp != NULL && fseek(m_fp, nOffset, nOrigin) == 0 ? S_OK : E_FAIL);
}

STDMETHODIMP CoFile::ReadString(BSTR* pbstrDest, long nMaxLen)
{
   if (m_fp != NULL)
   {
      char* pszStr = new char[nMaxLen + 1];
      HRESULT hr = fgets(pszStr, nMaxLen, m_fp) != NULL ? S_OK : E_FAIL;
      if (SUCCEEDED(hr))
      {
         int cchTemp = ::lstrlenA(pszStr);
         if (pszStr[cchTemp - 1] == '\n')
         {
            pszStr[--cchTemp] = 0;
         }
         OLECHAR* pwszTemp = new OLECHAR[++cchTemp];
         mbstowcs(pwszTemp, pszStr, cchTemp);
         *pbstrDest = ::SysAllocString(pwszTemp);
         delete[] pwszTemp;
      }
      delete[] pszStr;
      return (hr);
   }
   else
   {
      return (E_FAIL);
   }
}

STDMETHODIMP CoFile::WriteString(BSTR bstrSrc)
{
   if (m_fp != NULL)
   {
      UINT cbTemp = ::SysStringLen(bstrSrc) + 1;
      char* pszTemp = new char[cbTemp];
      wcstombs(pszTemp, bstrSrc, cbTemp);
      HRESULT hr = fprintf(m_fp, "%s\n", pszTemp) > 0 ? S_OK : E_FAIL;;
      delete[] pszTemp;
      return (hr);
   }
   else
   {
      return (E_FAIL);
   }
}

STDMETHODIMP CoFile::Tell(long* pnDest)
{
   if (m_fp != NULL)
   {
      *pnDest = ftell(m_fp);
      return (S_OK);
   }
   else
   {
      *pnDest = -1;
      return (E_FAIL);
   }
}

int CoFile::m_nNumObjs = 0;

Конструктор класса CoFile (строки 3…8) не претерпел никаких изменений, а вот реализация деструктора (строки 10…16) имеет существенные отличия. Поскольку автономный COM-сервер является обычным Windows-приложением и должен оставаться в памяти до тех пор, пока существует хотя бы один созданный им COM-объект, в нем организуется стандартный цикл обработки сообщений. После уничтожения всех объектов и полного разблокирования сервера (строка 12) необходимо вызвать функцию PostQuitMessage (строка 14) для прерывания цикла обработки сообщений, завершения функции WinMain и выгрузки сервера из памяти.

Методы интерфейса IUnknown (строки 18…57) реализованы стандартным образом; большинство методов, выполняющих «содержательную» часть работы класса CoFile, отличаются от предыдущего варианта только использованием параметров, имеющих OLE-automation-совместимые типы. Исключение составляют только методы Read (строки 79…96) и Write (строки 98…115), использующие для работы с передаваемым им «безопасным массивом» шаблонный класс CSafeArray, разработанный нами на шаге 0.

В методе Read, выполняющем чтение данных из файла в заданный буфер, создается экземпляр класса CSafeArray (строка 83), после чего созданный объект связывается с полученным указателем на буфер при помощи метода Attach (строка 84). Далее мы определяем емкость буфера как разницу между его верхней и нижней границами (строка 85) и блокируем массив, получая указатель на область памяти, в которую следует скопировать прочитанные данные (строка 86). После чтения данных из файла (строка 87) выполняется разблокирование массива (строка 88) и «отвязывание» от него объекта CSafeArray вызовом метода Detach (строка 89). Аналогичным образом построен и метод Write.

Отметим использование в заголовках всех реализованных методов еще двух «стандартных» макросов COM — STDMETHODIMP и STDMETHODIMP_, объявления которых содержатся в заголовочных файлах <basetyps.h> и <winnt.h>. Первый из них является «дополнением» макроса STDMETHOD и заменяется препроцессором C++ на

HRESULT __stdcall

а второй имеет «прототип»

STDMETHODIMP_(тип_возвр_знач)

и раскрывается в

тип_возвр_знач __stdcall

«дополняя», таким образом, макрос STDMETHOD_, используемый при объявлении метода интерфейса.

Шаг 5: объявление соответствующей фабрики класса.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// CoFileFact.h

class CoFileFactory: public IClassFactory
{
   friend class CoFile;
public:
   CoFileFactory(void);
   ~CoFileFactory(void);
public:
   STDMETHOD(QueryInterface)(REFIID iid, void** ppvDest);
   STDMETHOD_(ULONG, AddRef)(void);
   STDMETHOD_(ULONG, Release)(void);
public:
   STDMETHOD(CreateInstance)(IUnknown* pUnk, REFIID iid, void** ppvDest);
   STDMETHOD(LockServer)(BOOL fLock);
private:
   static int m_nNumLocks;
};

Поскольку в автономном сервере фабрика каждого класса существует в единственном экземпляре, создаваемом в начале работы этого сервера, у класса CoFileFactory, унаследованного от реализуемого им интерфейса IClassFactory (строка 3), отсутствует поле для хранения количества созданных объектов. Класс CoFile объявлен дружественным (строка 5) для предоставления ему доступа к закрытому полю m_nNumLocks, выполняющему роль счетчика блокировок сервера приложением-клиентом.

Шаг 6: реализация фабрики класса.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// CoFileFact.cpp

CoFileFactory::CoFileFactory(void)
{
}

CoFileFactory::~CoFileFactory(void)
{
}

STDMETHODIMP CoFileFactory::QueryInterface(REFIID iid, void** ppvDest)
{
   if (iid == IID_IUnknown)
   {
      *ppvDest = static_cast<IUnknown*>(this);
   }
   else if (iid == IID_IClassFactory)
   {
      *ppvDest = static_cast<IClassFactory*>(this);
   }
   else
   {
      *ppvDest = NULL;
      return (E_NOINTERFACE);
   }
   static_cast<IUnknown*>(*ppvDest)->AddRef();
   return (S_OK);
}

STDMETHODIMP_(ULONG) CoFileFactory::AddRef(void)
{
   return (1);
}

STDMETHODIMP_(ULONG) CoFileFactory::Release(void)
{
   return (1);
}

STDMETHODIMP CoFileFactory::CreateInstance(IUnknown* pUnk, REFIID iid, void** ppvDest)
{
   if (pUnk != NULL)
   {
      *ppvDest = NULL;
      return (CLASS_E_NOAGGREGATION);
   }
   else
   {
      CoFile* pCoFile = new CoFile();
      HRESULT hr = pCoFile->QueryInterface(iid, ppvDest);
      if (IS_ERROR(hr))
      {
         delete pCoFile;
      }
      return (hr);
   }
}

STDMETHODIMP CoFileFactory::LockServer(BOOL fLock)
{
   if (fLock)
   {
      ++m_nNumLocks;
   }
   else if (--m_nNumLocks == 0 && CoFile::m_nNumObjs == 0)
   {
      ::PostQuitMessage(0);
   }
   return (S_OK);
}

int CoFileFactory::m_nNumLocks = 0;

Конструктор (строки 3…5) и деструктор (строки 7…9) присутствуют только из формальных соображений и не выполняют никаких действий. Реализация метода QueryInterface (строки 11…28) также не блещет оригинальностью, а вот методы AddRef (строки 30…33) и Release (строки 35…38) выглядят несколько непривычно — каждый из них просто возвращает значение 1, поскольку счетчик ссылок у данного класса отсутствует за ненадобностью.

Вторым существенным отличием от «внутрипроцессного» варианта является реализация метода LockServer (строки 59…70): в случае, когда сервер полностью разблокирован и все созданные экземпляры класса CoFile уничтожены (строка 65), вызывается функция PostQuitMessage (строка 67), которая прерывает цикл обработки сообщений, что приводит к завершению функции WinMain и выгрузке сервера из памяти (напомним, что аналогичные действия выполняются в деструкторе класса CoFile, рассмотренном на шаге 4).

Шаг 7: описание библиотеки типов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// TypeInfo.odl

#include "Identifiers.h"

[uuid(S_UUID_FileServer), version(1.0)]
library FileServer
{
   importlib("stdole32.tlb");

   [object, uuid(S_UUID_IFileInit), oleautomation]
   interface IFileInit: IUnknown
   {
      HRESULT Open([in] BSTR bstrName, [in] BSTR bstrMode);
      HRESULT Close(void);
   };

   [object, uuid(S_UUID_IFileOperate), oleautomation]
   interface IFileOperate: IUnknown
   {
      HRESULT Read([out] SAFEARRAY(char) psaDest, [in] long nSize);
      HRESULT Write([in] SAFEARRAY(char) psaSrc, [in] long nSize);
      HRESULT Seek([in] long nOffset, [in] long nOrigin);
      HRESULT ReadString([out] BSTR* pbstrDest, [in] long nMaxLen);
      HRESULT WriteString([in] BSTR bstrSrc);
   };

   [object, uuid(S_UUID_IFileStatus), oleautomation]
   interface IFileStatus: IUnknown
   {
      HRESULT Tell([out] long* pnDest);
   };

   [uuid(S_UUID_CoFile), version(1.0)]
   coclass CoFile
   {
      interface IFileInit;
      interface IFileOperate;
      interface IFileStatus;
   };
};

Библиотека типов имеет имя «FileServer» (строка 6), ей назначены идентификатор и номер версии (строка 5); кроме того, в ней доступны все объявления из библиотеки типов stdole32.tlb (строка 8).

Интерфейс IFileInit (строки 10…15) объявлен как потомок IUnknown (строка 11); он относится к COM, имеет обязательный идентификатор и является OLE-automation-совместимым (строка 10). Аналогичным образом выполнены объявления интерфейсов IFileOperate (строки 17…25) и IFileStatus (строки 27…31).

COM-классу CoFile (строки 33…39), реализующему перечисленные интерфейсы (строки 36…38), назначены обязательный идентификатор и номер версии (строка 33).

Шаг 8: Реализация точки входа COM-сервера.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// EntryPoint.cpp

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPTSTR pszCmdLine, int nShowCmd)
{
   static const TCHAR szProgID[] = _T("Zarezky.CoFile.1");
   static const TCHAR szClassID[] = _T(S_UUID_CoFile);

   TCHAR szKey[80];
   TCHAR szValue[64];
   ITypeLib* pTypeLib;
   DWORD dwFactID;
   MSG msg;

   if (!SUCCEEDED(::CoInitialize(NULL)))
   {
      return (-1);
   }

   if (::lstrcmpi(pszCmdLine, _T("/Register")) == 0)
   {
      LPTSTR pszSrvName = new TCHAR[_MAX_PATH];
      ::GetModuleFileName(hInst, pszSrvName, _MAX_PATH);
      ::wsprintf(szKey, _T("CLSID\\{%s}"), szClassID);
      ::RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, szProgID, ::lstrlen(szProgID) + 1);
      ::wsprintf(szKey, _T("CLSID\\{%s}\\LocalServer32"), szClassID);
      ::RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, pszSrvName, ::lstrlen(pszSrvName) + 1);
      ::wsprintf(szKey, _T("%s\\CLSID"), szProgID);
      ::wsprintf(szValue, _T("{%s}"), szClassID);
      ::RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, szValue, ::lstrlen(szValue) + 1);
#if defined(_UNICODE)
      ::LoadTypeLibEx(pszSrvName, REGKIND_NONE, &pTypeLib);
      ::RegisterTypeLib(pTypeLib, pszSrvName, NULL);
#else
      int nSize = mbstowcs(NULL, pszSrvName, 0) + 1;
      OLECHAR* pwszTemp = new OLECHAR[nSize];
      mbstowcs(pwszTemp, pszSrvName, nSize);
      ::LoadTypeLibEx(pwszTemp, REGKIND_NONE, &pTypeLib);
      ::RegisterTypeLib(pTypeLib, pwszTemp, NULL);
      delete[] pwszTemp;
#endif
      pTypeLib->Release();
      delete[] pszSrvName;
   }
   else if (::lstrcmpi(pszCmdLine, _T("/Unregister")) == 0)
   {
      LCID locID = MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), SORT_DEFAULT);
      ::UnRegisterTypeLib(LIBID_FileServer, 1, 0, locID, SYS_WIN32);
      ::SHDeleteKey(HKEY_CLASSES_ROOT, szProgID);
      ::wsprintf(szKey, _T("CLSID\\{%s}"), szClassID);
      ::SHDeleteKey(HKEY_CLASSES_ROOT, szKey);
   }
   else if (_tcsstr(pszCmdLine, _T("Embedding")) != 0)
   {
      CoFileFactory* pFact = new CoFileFactory();
      ::CoRegisterClassObject(CLSID_CoFile, pFact, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwFactID);
      while (::GetMessage(&msg, NULL, 0, 0))
      {
         ::TranslateMessage(&msg);
         ::DispatchMessage(&msg);
      }
      ::CoRevokeClassObject(dwFactID);
      delete pFact;
   }

   ::CoFreeUnusedLibraries();
   ::CoUninitialize();

   return (0);
}

В самом начале своей работы сервер инициализирует COM-библиотеку вызовом функции CoInitialize (строка 14); дальнейшие действия зависят от содержания командной строки, указанной при его запуске. При запуске с ключом «/Register» сервер определяет свое полное имя (строка 22), после чего регистрирует в системном реестре класс CoFile (строки 23…29) и библиотеку типов (строки 30…41), находящуюся в его ресурсах. При запуске с ключом «/Unregister» (строка 44) выполняются обратные действия: из системного реестра удаляется информация о библиотеке типов (строки 46…47) и классе CoFile (строки 48…50).

И наконец, если в командной строке отсутствует слово «Embedding» (строка 52), то создается экземпляр фабрики класса (строка 54), который регистрируется в COM-библиотеке (строка 55), после чего создается цикл обработки сообщений (строки 56…60). После того, как этот цикл будет прерван разблокированием сервера и уничтожением всех созданных им COM-объектов, из COM-библиотеки удаляется информация о фабрике класса (строка 61), которая затем уничтожается навеки (строка 62).

Завершая свое выполнение, сервер «расстается» с COM-библиотекой, вызывая функцию CoUninitialize (строка 66).

обновлено
29.03.2006
 
Проверка PR и ТИЦ