Выбрать главу

#define REFGUID const GUID&

#define REFIID const IID&

#define REFCLSID const CLSID&

Чтобы иметь возможность сравнивать величины GUID, СОМ обеспечивает функции эквивалентности и перегружает операторы == и != для постоянных ссылок GUID:

inline BOOL IsEqualGUID(REFGUID r1, REFGUID r2)

{

return !memcmp(&r1, &r2, sizeof(GUID));

}

#def1ne IsEqualIID(r1, r2) IsEqualGUID((r1) , (r2))

#define IsEqualCLSID(r1, r2) IsEqualGUID((r1), (r2))

inline BOOL operator == (REFGUID r1, REFGUID r2)

{

return !memcmp(&r1, &r2, sizeof(GUID));

}

inline BOOL operator != (REFGUID r1, REFGUID r2)

{

return !(r1 == r2);

}

Фактические заголовки SDK содержат условно компилируемые совместимые с С версии определений типа, макросов и встраиваемых функций, как показано выше.

Поскольку показано, что представления имен интерфейсов на этапе выполнения являются GUID, а не строками; это означает, что метод Dynamic_Cast, описанный в предыдущей главе, следует пересмотреть. Действительно, весь интерфейс IЕхtensibleObject должен быть изменен и преобразован в свой аналог IUnknown, совместимый с СОМ. 

Интерфейс IUnknown

СОМ-интерфейс IUnknown имеет то же назначение, что и интерфейс IExtensibleObject, определенный в предыдущей главе. Последняя версия IExtensibleObject, появившаяся в конце предыдущей главы, имеет вид:

class IExtensibleObject

{

public:

virtual void *Dynamic_Cast(const char* pszType) = 0;

virtual void DuplicatePointer(void) = 0;

virtual void DestroyPointer(void) = 0;

}

Для определения типа на этапе выполнения был применен метод Dynamic_Cast, аналогичный оператору C++ dynamic_cast. Для извещения объекта о том, что указатель интерфейса дублировался, использовался метод DuplicatePointer. Для сообщения объекту, что указатель интерфейса уничтожен и все используемые им ресурсы могут быть освобождены, был применен метод DestroyPointer. Вот как выглядит определение IUnknown на C++:

extern "С" const IID IID_IUnknown: interface IUnknown

{

virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) = 0;

virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;

virtual ULONG STDMETHODCALLTYPE Release(void) = 0;

};

Заголовочные файлы SDK дают псевдоним interface ключевому слову C++ struct, используя препроцессор С. Поскольку интерфейсы в СОМ определены не как классы, а как структуры, то для того, чтобы сделать методы интерфейса общедоступными, ключевое слово public не требуется. Чтобы создать для целевой платформы СОМ-совместимые стековые фреймы, необходим макрос STDMETHODCALLTYPE. Если целевыми являются платформы Win32, то при использовании компилятора Microsoft C++ этот макрос раскрывается в _stdcall.

IUnknown функционально эквивалентен IExtensibleObject. Метод QueryInterface используется для динамического определения типа и аналогичен С++-оператору dynamic_cast. Метод AddRef используется для сообщения объекту, что указатель интерфейса дублирован. Метод Release используется для сообщения объекту, что указатель интерфейса уничтожен и все ресурсы, которые объект поддерживал от имени клиента, могут быть отключены. Главное различие между IUnknown и интерфейсом, определенным в предыдущей главе, заключается в том, что IUnknown использует идентификаторы GUID, а не строки для идентификации типов интерфейса на этапе выполнения.

IDL-определение IUnknown можно найти в файле unknwn.idl из директории SDK, содержащей заголовочные файлы:

// unknwn.idl – system IDL file

// unknwn.idl – системный файл IDL

[ local, object, uuid (00000000-0000-0000-C000-000000000046) ] interface IUnknown

{

HRESULT QueryInterface([in] REFIID riid, [out] void **ppv);

ULONG AddRef(void); ULONG Release(void);

}

Атрибут local подавляет генерирование сетевого кода для этого интерфейса. Этот атрибут необходим для того, чтобы смягчить требования СОМ о том, что все методы при вызове с удаленных машин должны возвращать HRESULT. Как будет показано в следующих главах, интерфейс IUnknown трактуется особым образом при работе с удаленными объектами. Заметим, что фактические, то есть использующиеся на практике IDL-описания интерфейсов, которые содержатся в заголовках SDK, немного отличаются от определений, данных в этой книге. Фактические определения часто содержат дополнительные атрибуты для оптимизации генерируемого сетевого кода, которые не имеют отношения к нашему обсуждению. В случае сомнений обратитесь за полными определениями к последней версии заголовочных файлов SDK.

Интерфейс IUnknown является родительским для всех СОМ-интерфейсов. IUnknown – единственный интерфейс СОМ, который не наследует от другого интерфейса. Любой другой допустимый интерфейс СОМ должен быть прямым потомком IUnknown или какого-нибудь другого допустимого интерфейса СОМ, который, в свою очередь, должен сам наследовать или прямо от IUnknown, или от какого-нибудь другого допустимого интерфейса СОМ. Это означает, что на двоичном уровне все интерфейсы СОМ являются указателями на таблицы vtbl, которые начинаются с трех точек входа: QueryInterface, AddRef и Release. Все специфические для интерфейсов методы будут иметь точки входа в vtbl, которые появляются после этих трех общих точек входа.

Чтобы наследовать от интерфейса IDL, нужно или определить базовый интерфейс в том же IDL-файле, или использовать директиву import, чтобы сделать внешнее IDL-определение базового интерфейса явным в данной области действия:

// calculator.idl

[object, uuid(BDA4A270-A1BA-11dO-8C2C-0080C73925BA)]

interface ICalculator : IUnknown

{

import «unknwn.idl»;

// bring in def. of IUnknown

// импортируем определение IUnknown

HRESULT Clear(void);

HRESULT Add([in] long n);

HRESULT Sum([out, retval] long *pn);

}

Оператор import может появляться или внутри определения интерфейса, как показано здесь, или предшествовать описанию интерфейса в глобальной области действия. В любом из этих случаев действия оператора import одинаковы, он может многократно импортировать один IDL-файл без всякого ущерба. Поскольку сгенерированный C/C++ заголовочный файл будет требовать С/С++-версии импортируемого IDL-файла, чтобы обеспечить наследование, оператор import из IDL-файла будет странслирован в команду #include в генерируемом заголовочном С/С++-файле:

// calculator.h – generated by MIDL

// calculator.h – генерированный MIDL

// bring in def. of IUnknown

// вводим определения IUnknown

#include «unknwn.h»

extern "C" const IID IID_ICalculator;

interface ICalculator : public IUnknown

{

virtual HRESULT STDMETHODCALLTYPE Clear(void) = 0;

virtual HRESULT STDMETHODCALLTYPE Add(long n) = 0;

virtual HRESULT STDMETHODCALLTYPE Sum(long *pn) = 0;

}

Компилятор MIDL также создаст С-файл, содержащий фактические определения всех GUID, имеющихся в исходном IDL-файле:

// calculator_i.с – generated by MIDL

const IID IID_ICalculator =

{ 0xBDA4A270, 0xA1BA, 0x11d0, { 0x8C, 0x2C, 

0x00, 0х80, 0хC7, 0х39, 0x25, 0xBA } };

Каждый проект, который будет использовать этот интерфейс, должен или добавить calculator_i.c к своему файлу сборки (makefile), или включить calculator_i.c в один из исходных файлов на С или C++ с использованием препроцессора С. Если это не сделано, то идентификатору IID_ICalculator не будет выделено памяти для его 128-битного значения и проект не будет скомпонован по причине неразрешенных внешних идентификаторов.

СОМ не накладывает никаких ограничений на глубину иерархии интерфейсов при условии, что конечным базовым интерфейсом является IUnknown. Нижеследующий IDL является вполне допустимым и корректным для СОМ: