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
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).