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

Технология, основанная на использовании композиции для реализации интерфейсов, требует значительно больше кода, чем при простом множественном наследовании. Кроме того, качество генерированного кода, вероятно, не лучше (а возможно, и хуже), чем в случае множественного наследования. Из того факта, что классу CarPlane не понадобилось наследовать ни одному интерфейсу СОМ, следует, что композиция является разумной технологией для внесения СОМ в старые библиотеки классов. Например, MFC (Microsoft Foundation Classes – библиотека базовых классов Microsoft) использует эту технологию. Причиной применения композиции при реализации новых классов является получение отдельных реализации метода, определенного одинаково более чем в одном интерфейсе. К счастью, стандартные интерфейсы, определяемые СОМ, очень редко создают такие конфликты, а те немногие, которые создают, почти всегда преобразуются в семантически эквивалентные функции. Для разрешения коллизий, подобных тем, что произошли в сценарии с GetMaxSpeed , композиция, вероятно, и не требуется, так как в первом приближении для преобразования двойников в уникальные объекты достаточно использования промежуточных классов. Эта методика проста, эффективна и фактически не требует дополнительного кода. Основная причина использования композиции в новом коде заключается в том, что нужно обеспечить подсчет ссылок в каждом интерфейсе.

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

STDMETHODIMP QueryInterface(REFIID riid, void **ppv)

{

if (riid == IID_IBoat)

{

// allocate resource the first time through

// размещаем ресурсы при первом проходе

if (m_pTonsOfMemory == 0) m_pTonsOfMemory = new char[4096 * 4096];

*ppv = static_cast<IBoat*>(this);

}

else if

}

не существует способа определить момент, когда больше нет внешних указателей интерфейса IBoat, так как вызов Release, который клиент делает через интерфейс IBoat, неотличим от вызова Release, сделанного через любой другой интерфейс объекта. В обычной ситуации именно это и нужно, но в данном случае вызовы AddRef и Release через интерфейсы IBoat необходимо рассматривать иначе. Если бы интерфейс IBoat был реализован с использованием композиции, то он имел бы свои собственные уникальные реализации AddRef и Release, в которых он мог бы поддерживать свой собственный счетчик ссылок, отличный от счетчика главного объекта:

class CarBoatPlane : public ICar, public IPlane

{

LONG m_cRef;

char *m_pTonsOfMemory;

CarBoatPlane (void) : m_cRef(0),

m_pTonsOfMemory (0) {}

public:

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

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

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

STDMETHODIMP GetMaxSpeed(long *pMax);

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

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<BaseName*>(\

reinterpret_cast<MemberType*>(0x10000000 + \

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

#define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(Req,\

MemberType, MemberName) \