Сущность технологии СОМ. Библиотека программиста
Шрифт:
// composite to map distinguished IUnknown vptr to
// non-delegating InternalXXX routines in main object
// композит для преобразования определенного vptr IUnknown
// в неделегирующие подпрограммы InternalXXX в главном
// объекте
class XNDUnknown : public IUnknown
{ Car* This
{
return (Car*)((BYTE*)this – offsetof(Car, m_innerUnknown));
}
STDMETHODIMP QueryInterface(REFIID r, void**p)
{
return This->InternalQueryInterface(r,p);
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return This->InternalAddRef;
}
STDMETHODIMP_(ULONG) Release(void)
{
return This->InternalRelease;
}
};
XNDUnknown m_innerUnknown;
// composite instance
//
Двоичное размещение этого объекта показано на рис. 4.8. Методы делегирования класса чрезвычайно просты:
STDMETHODIMP Car::QueryInterface(REFIID riid, void **ppv) { return m_pUnkOuter->QueryInterface(riid, ppv); }
STDMETHODIMP_(ULONG) Car::AddRef(void) { return m_pUnkOuter->AddRef; }
STDMETHODIMP_(ULONG) Car::Release (void) { return m_pUnkOuter->Release; }
Эти подпрограммы являются версиями, которые будут заполнять таблицы vtbl всех интерфейсов объекта, так что какой бы интерфейс клиент ни получил, методы IUnknown всегда передают функции основной идентификационной единице объекта.
Для того чтобы объект можно было использовать в обоих сценариях – агрегирования и автономном – разработчик объекта должен установить свой элемент данных m_pUnkOuter так, чтобы в случае автономного режима он указывал на собственный неделегирующий IUnknown:
Car::Car(IUnknown *pUnkOuter)
{
if (pUnkOuter)
// delegate to pUnkOuter
// делегируем в pUnkOuter
m_pUnkOuter = pUnkOuter;
else // delegate to non-delegating self
// делегируем неделегирующему себе m_pUnkOuter = &m_innerUnknown;
}
Разработчик обеспечивает то, что в обоих случаях m_pUnkOuter указывает на нужную для данного объекта реализацию QueryInterface, AddRef и Release.
Обычные неделегирующие реализации QueryInterface, AddRef и Release являются вполне правильными и предсказуемыми:
STDMETHODIMP Car::InternalQueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown) *ppv = static_cast<IUnknown*>(&m_innerUnknown);
else if (riid = IID_IVehicle) *ppv = static_cast<IVehicle*>(this);
else if (riid == IID_ICar) *ppv = static_cast<ICar*>(this);
else return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef;
return S_OK;
}
STDMETHODIMP_(ULONG) Car::InternalAddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Car::InternalRelease(void)
{
ULONG res = InterlockedDecrement(&m_cRef);
if (res == 0) delete this;
return res;
}
Единственной отличительной особенностью этих трех методов (кроме их имен) является то, что InternalQueryInterface при запросе IUnknown возвращает указатель на неделегирующую Unknown.
И наконец, подпрограмму создания Car требуется модифицировать для поддержки агрегирования:
STDMETHODIMP CarClass::CreateInstance(IUnknown *punk0uter, REFIID riid, void **ppv)
{
// verify that aggregator only requests IUnknown as
// initial interface
// проверяем, что агрегатор только запрашивает IUnknown как
// начальный интерфейс
if (pUnkOuter != 0 && riid != IID_IUnknown)
return (*ppv = 0), E_INVALIDARG;
// create new object/aggregate
// создаем новый объект или агрегат Car
*р = new Car(pUnkOuter);
if (!p) return (*ppv = 0), E_OUTOFMEMORY;
// return resultant pointer
// возвращаем результирующий указатель
p->InternalAddRef;
HRESULT hr = p->InternalQueryInterface(riid, ppv);
p->InternalRelease;
return hr;
}
Отметим, что здесь используются неделегирующие версии QueryInterface, AddRef и Release. Если создается автономная идентификационная единица, то это, конечно, допустимо. Если же создается агрегат, то необходимо убедиться, что AddRef обработал внутренний, а не внешний объект. Отметим также, что внешний объект в качестве начального интерфейса должен запросить IUnknown. Все это регламентировано Спецификацией СОМ. Если бы внешний объект мог запрашивать произвольный начальный интерфейс, то внутреннему объекту пришлось бы хранить два дублирующих набора указателей vptr: один набор делегировал бы свои реализации QueryInterface, AddRef и Release, а другой – нет. При допущении в качестве начального интерфейса одного IUnknown разработчик объекта может выделить только один vptr, который будет действовать как неделегирующий IUnknown.
При программировании с СОМ-агрегированием может возникнуть опасность, связанная со счетчиком ссылок. Отметим, что разработчик внутреннего объекта дублирует указатель на управляющий внешний объект, но не вызывает AddRef. Вызов AddRef в данной ситуации запрещен, поскольку если оба объекта будут обрабатывать друг друга посредством AddRef, то получится бесконечный цикл. Правила подсчета ссылок при агрегировании требуют, чтобы внешний объект хранил указатель на внутренний неделегирующий IUnknown объекта (это указатель, возвращенный подпрограммой создания объекта) после подсчета ссылок на этот указатель. Внутренний объект хранит указатель на IUnknown управляющего внешнего объекта с неподсчитанными ссылками. Формально эти соотношения зафиксированы в специальной формулировке правил СОМ для счетчиков ссылок. Вообще-то методику использования указателей без подсчета ссылок применять нельзя, поскольку ее невозможно реализовать в случае удаленного доступа к объектам. Более эффективный способ избежать зацикливания счетчика ссылок состоит в том, чтобы ввести промежуточные идентификационные единицы (identities) объектов, счетчики ссылок которых не повлияют на время жизни никакого объекта.