В приведенном выше определении класса опущены только определения методов объекта вне 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).
Реализация отделяемого интерфейса подобна реализации интерфейса с использованием композиции. Для отделяемого интерфейса должен быть определен второй класс, наследующий тому интерфейсу, который он будет реализовывать. Чтобы обеспечить идентификацию, QueryInterface отделяемого интерфейса должен делегировать управление функции QueryInterface основного класса. Два основных различия заключаются в том, что:
1) главный объект динамически размещает отделяемый интерфейс вместо того, чтобы иметь элемент данных экземпляра, и
2) отделяемый композит должен содержать явный обратный указатель на главный объект, так как технология фиксированного смещения, используемая в композиции, здесь не работает, поскольку отделяемый интерфейс изолирован от основного объекта. Следующий класс реализует IBoat как отделяемый интерфейс:
class CarBoat : public ICar
{
LONG m_cRef;
CarBoat (void): m_cRef(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);
// define nested class that implements IBoat
// определяем вложенный класс, реализующий IBoat
struct XBoat : public IBoat
{
LONG m_cBoatRef;
// back pointer to main object is explicit member
// обратный указатель на главный объект – явный член
CarBoat *m_pThis;
inline CarBoat* This()
{
Читать дальше