Изменить стиль страницы

STDMETHODIMP Brake(void);

// IPlane methods – методы IPlane

STDMETHODIMP TakeOff(void);

// define nested class that implements IBoat

// определяем вложенный класс, реализующий IBoat

struct XBoat : public IBoat {

// get back pointer to main object

// получаем обратный указатель на главный объект

inline CarBoatPlane* This();

LONG m_cBoatRef;

// per-interface ref count

// счетчик ссылок на каждый интерфейс

XBoat(void) : m_cBoatRef(0) {}

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

STDMETHODIMP GetMaxSpeed(long *pval);

STDMETHODIMP Sink(void);

};

XBoat m_xBoat; };

Реализация AddRef и Release из IBoat могут теперь следить за числом ссылок типа IBoat и высвободить ресурсы, когда они больше не нужны:

STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::AddRef()

{

ULONG res = InterlockedIncrement(&m_cBoatRef);

if (res == 1)

{

// first AddRef – первый AddRef

// allocate resource and forward AddRef to object

// размещаем ресурсы и пересылаем AddRef на объект

This()->m_pTonsOfMemory = new char[4096*4096];

This()->AddRef(); }

return res; }

STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::Release()

{

ULONG res = InterlockedDecrement(&m_cBoatRef);

if (res == 0) {

// last Release – последний Release

// free resource and forward Release to object

// освобождаем ресурсы и пересылаем Release на объект

delete [] This()->m_pTonsOfMemory;

This()->Release();

} return res; }

Чтобы эта методика работала, все пользующиеся интерфейсными указателями должны придерживаться требований спецификации СОМ: функция Release должна вызываться через указатель, посредством которого вызывается соответствующая функция AddRef. Поэтому правильной концовкой QueryInterface будет следующая:

((IUnknown*)(*ppv))->AddRef();

// use exact ptr

// используем точный указатель return S_OK;

вместо такого:

AddRef();

// just call this->AddRef

// только вызов

this->AddRef return S_OK;

Первый вариант гарантирует, что если клиент пишет следующий правильный код

IBoat *pBoat = 0;

HRESULT hr = pUnk->QueryInterface(IID_IBoat, (void**)&pBoat);

if (SUCCEEDED(hr))

{ hr = pBoat->Sink(); pBoat->Release(); }

то для AddRef и для Release обязательно будет использовано одно и то же значение указателя.

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

class CarBoatPlane : public ICar, public IPlane

{ public: struct XBoat : public IBoat

{

// composite QI/AddRef/Release/This()

// композит из QI/AddRef/Release/This()

IMPLEMENT_COMPOSITE_UNKNOWN(CarBoatPlane, XBoat, m_xBoat) STDMETHODIMP GetMaxSpeed(long *pval);

STDMETHODIMP Sink(void);

};

XBoat m_xBoat;

// IVehicle methods

// методы IVehicle

STDMETHODIMP GetMaxSpeed(long *pMax);

// ICar methods

// методы ICar

STDMETHODIMP Brake(void);

// IPlane methods

// методы IPlane

STDMETHODIMP TakeOff(void);

// standard heap-based QI/AddRef/Release

// стандартные расположенные в «куче» QI/AddRef/Release

IMPLEMENT_UNKNOWN(CarBoatPlane)

BEGIN_INTERFACE_TABLE(CarBoatPlane)

IMPLEMENTS_INTERFACE_AS(IVehicle, ICar)

IMPLEMENTS_INTERFACE(ICar)

IMPLEMENTS_INTERFACE(IPlane)

// macro that calculates offset of data member

// макрос, вычисляющий смещение элемента данных

IMPLEMENTS_INTERFACE_WITH_COMPOSITE(IBoat, XBoat, m_xBoat)

END_INTERFACE_TABLE() };

В приведенном выше определении класса опущены только определения методов объекта вне QueryInterfасе, AddRef и Release. Два новых макроса, использованных в определении класса, определяются следующим образом:

// inttable.h

// (book-specific header file)

// (заголовочный файл, специфический для данной книги)

#define COMPOSITE_OFFSET(ClassName, BaseName, \

MemberType, MemberName) \

(DWORD(static_cast(\

reinterpret_cast(0x10000000 + \

offsetof(ClassName, MemberName)))) – 0х10000000)

#define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(Req,\

MemberType, MemberName) \

{ &IID_##Req,ENTRY_IS_OFFSET, COMPOSITE_OFFSET(_IT,\

Req, MemberType, MemberName) },

// impunk.h

// (book-specific header file)

// (заголовочный файл, специфический для данной книги)

#define IMPLEMENT_COMPOSITE_UNKNOWN(OuterClassName,\

InnerClassName, DataMemberName) \

OuterClassName *This() \

{ return (OuterClassName*)((char*)this – \

offsetof(OuterClassName, DataMemberName)); }\

STDMETHODIMP QueryInterface(REFIID riid, void **ppv)\

{ return This()->QueryInterface(riid, ppv); }\

STDMETHODIMP_(ULONG) AddRef(void) \

{ return This()->AddRef(); }\

STDMETHODIMP_(ULONG) Release(void) \

{ return This()->Release(); }

Эти макросы препроцессора просто дублируют фактические реализации QueryInterface, AddRef и Release , использованные в композиции.

Динамическая композиция

Если для реализации интерфейса в классе C++ используется множественное наследование или композиция, то в каждом объекте этого класса будут содержаться служебные данные (overhead) указателя vptr размером в четыре байта на каждый поддерживаемый интерфейс (принимая, что sizeof (void*) == 4). Если число интерфейсов, экспортируемых объектом, невелико, то эти служебные данные не играют важной роли, особенно в свете преимуществ, предоставляемых программной моделью СОМ. Если, однако, число поддерживаемых интерфейсов велико, то размер служебных данных vptr может вырасти до такой степени, что часть объекта, не связанная с СОМ, будет казаться маленькой по сравнению с ними. При использовании каждого из этих интерфейсов все время без служебных данных не обойтись. Если же, однако, эти интерфейсы не будут использоваться никогда или использоваться в течение короткого времени, то можно воспользоваться лазейкой в Спецификации СОМ и оптимизировать vptr некоторых неиспользуемых объектов.

Вспомним правило, гласящее, что все запросы QueryInterface на объект относительно IUnknown должны возвращать точно такое же значение указателя. Именно так в СОМ обеспечивается идентификация объектов. В то же время Спецификация СОМ определенно разрешает возвращать другие значения указателей в ответ на запросы QueryInterface относительно любых других типов интерфейсов, кроме IUnknown. Это означает, что для нечасто используемых интерфейсов объект может динамически выделять память для vptr по требованию, не заботясь о возврате того же самого динамически выделенного блока памяти каждый раз, когда запрашивается какой-либо интерфейс. Эта технология временного (transient) размещения композитов впервые была описана в «белой книге» Microsoft Поваренная книга для программистов СОМ (Microsoft white paper The СОМ Programmer's Cookbook), написанной Криспином Госвеллом (Crispin Goswell) (http://www.microsoft.com/oledev). В этой «белой книге» такие временные интерфейсы называются отделяемыми (tearoff).