Чтение онлайн

ЖАНРЫ

Сущность технологии СОМ. Библиотека программиста
Шрифт:

CarBoat *m_pThis;

inline CarBoat* This

{

return m_pThis;

}

XBoat(CarBoat *pThis);

~XBoat(void);

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

STDMETHODIMP GetMaxSpeed(long *pval);

STDMETHODIMP Sink(void);

};

// note: no data member of type Xboat

// заметим: нет элементов данных типа Xboat

};

Для QueryInterface главного объекта необходимо динамически разместить новый отделяемый интерфейс – каждый раз, когда запрашивается IBoat:

STDMETHODIMP CarBoat::QueryInterface(REFIID riid, void **ppv)

{

if (riid == IID_IBoat)

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

else if (riid == IID_IUnknown)

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

:

:

Каждый

раз при получении запроса на интерфейс IBoat размещается новый отделяемый интерфейс. Согласно стандартной практике QueryInterface вызова AddRef посредством результирующего указателя: ((IUnknown*)*ppv)->AddRef;

AddRef будет обрабатывать непосредственно из QueryInterface только отделяемый интерфейс. Важно то, что главный объект остается в памяти столько времени, сколько существует отделяемый интерфейс. Простейший путь обеспечить это – заставить сам отделяемый интерфейс представлять неосвобожденную ссылку. Это можно реализовать в разработчике и деструкторе отделяемого интерфейса:

CarBoat::XBoat::XBoat(CarBoat *pThis) : m_cBoatRef(0), m_pThis(pThis)

{

m_pThis->AddRef;

}

CarBoat::XBoat::~XBoat(void)

{

m_pThis->Release;

}

Как и в случае с композицией, методу QueryInterface отделяемого интерфейса требуется идентифицировать объект, делегируя освобождение функции главного объекта. Однако отделяемый интерфейс может выявлять запросы на тот интерфейс (интерфейсы), который он сам реализует, и просто возвращать указатель, обработанный AddRef, себе самому:

STDMETHODIMP CarBoat::XBoat::QueryInterface(REFIID riid, void**ppv)

{

if (riid != IID_IBoat) return This->QueryInterface(riid, ppv);

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

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

return S_OK;

}

Ввиду того, что отделяемый интерфейс должен самоуничтожаться, когда он больше не нужен, он должен поддерживать свой собственный счетчик ссылок и уничтожать себя, когда этот счетчик достигнет нуля. Как отмечалось ранее, деструктор отделяемого интерфейса освободит главный объект до того, как сам исчезнет из памяти:

STDMETHODIMP_(ULONG) CarBoat::XBoat::AddRef (void)

{

return InterlockedIncrement(&m_cRef);

}

STDMETHODIMP_(ULONG) CarBoat::XBoat::Release(void)

{

ULONG res = InterlockedDecrement(&m_cBoatRef);

if (res == 0) delete this;

// dtor releases main object

// деструктор освобождает главный объект

return res;

}

Как и в случае с композицией, метод This можно использовать в любых методах отделяемого интерфейса, которым требуется получить статус главного объекта. Разница состоит в том, что отделяемым интерфейсам требуется явный обратный указатель, в то время как нормальные композиты могут использовать фиксированные смещения, выделяя по четыре байта на композит.

На первый взгляд, отделяемые интерфейсы кажутся лучшей из всех возможностей. Когда интерфейс не используется, то на его служебные данные отводится нуль байт объекта. Когда же интерфейс используется, объект косвенно тратит 4 байта на служебные данные отделяемого интерфейса. Подобное впечатление базируется на нескольких обманчивых предположениях. Во-первых, затраты на работающий отделяемый интерфейс составляют отнюдь не только 4 байта памяти для его vptr. Отделяемому интерфейсу требуются также обратный указатель и счетчик ссылок [1] . Во-вторых, несмотря на возможность использования специального распределителя памяти (custom memory allocator ), отделяемому интерфейсу потребуется по крайней мере 4 дополнительных байта на выравнивание и/или заполнение заголовков динамически выделенной памяти, используемых С-библиотекой для реализации malloc/operator new . Это означает, что объект действительно экономит 4 байта, когда интерфейс не используется. Но когда интерфейс используется, отделяемый интерфейс тратит как минимум 12 байт, если подключен специальный распределитель памяти, и 16 байт, если, по умолчанию,

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

1 Служебные данные счетчика ссылок можно сократить, если разработчик желает ограничить клиентское использование AddRef. Это очень опасная оптимизация, возникшая благодаря растущей популярности интеллектуальных указателей, и результатом ее часто является наличие избыточных (но безвредных) пар AddRef/Release.

К сожалению, дело с отделяемым интерфейсом обстоит еще хуже. Как видно из показанной ранее реализации, если объект получает два запроса QueryInterface на тот же самый отделяемый интерфейс, то будут созданы две копии этого отделяемого интерфейса, так как указатель на первый из них полностью забывается главным объектом, поскольку он был возвращен вызывающему объекту. Это означает, что в этом случае отделяемый интерфейс занимает по крайней мере от 24 до 32 байт, так как в памяти находятся оба vptr отделяемого интерфейса, по одному на каждый запрос QueryInterface. Эта память не будет восстановлена, пока клиент не освободит каждый отделяемый интерфейс. Ситуация, когда два запроса QueryInterface удерживают указатель в течение всего времени жизни объекта, особенно важна, так как именно это и происходит при удаленном обращении к объекту. СОМ-слой, реализующий удаленные вызовы, будет дважды запрашивать объект (с помощью QueryInterface) на предмет одного и того же интерфейса и будет удерживать оба результата в течение всего времени жизни объекта. Это обстоятельство делает отделяемые интерфейсы особенно рискованными для объектов, к которым может осуществляться удаленный доступ.

Узнав обо всех подводных камнях отделяемых интерфейсов, задаешь себе логичный вопрос: "В каких же случаях отделяемые интерфейсы являются подходящими?" Не существует безусловного ответа; в то же время отделяемые интерфейсы очень хороши для поддержки большого числа взаимно исключающих интерфейсов. Рассмотрим случай, в котором в дополнение к трем транспортным интерфейсам, показанным ранее, имеются интерфейсы ITruck (грузовик), IMonsterТruck (грузовик-монстр), IMotorcycle (мотоцикл), IBicycle (велосипед), IUnicycle (уницикл), ISkateboard (скейтборд) и IHelicopter (вертолет), причем все они наследуют IVehicle. Если бы производящий транспортный класс хотел поддерживать любой из этих интерфейсов, но только по одному из них для каждого заданного экземпляра, то для осуществления этого отделяемые интерфейсы были бы прекрасным способом при условии, что главный объект кэшировал бы указатель на первый отделяемый интерфейс. Определение класса главного объекта выглядело бы примерно так:

class GenericVehicle : public IUnknown

{

LONG m_cRef;

IVehicle *m_pTearOff;

// cached ptr to tearoff

// кэшированный указатель на отделяемый интерфейс

GenericVehicle(void) : m_cRef(0), m_pTearOff(0) {}

// IUnknown methods

// методы IUnknown

STDMETHODIMP QueryInterface(REFIID, void **);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release (void);

// define tearoff classes

// определяем классы отделяемых интерфейсов

class XTruck : public ITruck { … };

class XMonsterTruck : public IMonsterTruck { … };

class XBicycle : public IBicycle { … };

:

:

:

};

В этом классе в случае, когда не используется ни один из интерфейсов, объект платит за пустой кэшированный указатель только четырьмя дополнительными байтами. Когда приходит запрос QueryInterfасе на один из десяти транспортных интерфейсов, то память выделена для нового отделяемого интерфейса один раз и кэширована для более позднего использования:

Поделиться с друзьями: