{

// similar treatment for IBoat and IPlane

// IBoat и IPlane обрабатываются сходным образом

}

};

Из требования, чтобы множество поддерживаемых интерфейсов было статичным, следует простой вывод, что разработчикам объектов не разрешается создавать конструкции, состоящие из одного объекта, который дает два различных ответа «да/нет» на запрос определенного интерфейса. Одна из причин того, что иерархия типов объекта должна оставаться неизменной на всем протяжении своего жизненного цикла, состоит в том, что СОМ не гарантирует отправления всех клиентских запросов QueryInterface такому объекту в случае, когда к нему имеется удаленный доступ. Неизменность иерархии типов позволяет «заместителям» на стороне клиента (client-side proxies) кэшировать результаты QueryInterface во избежание чрезмерных обменов клиент-объект. Такая оптимизация очень важна для эффективности СОМ, но она разрушает конструкции, использующие QueryInterface для передачи динамической семантической информации вызывающему объекту.

Единственность и идентификация

Предыдущий раздел был посвящен запросам QueryInterface, которые представляют собой ответы типа «да/нет» вызывающим объектам. QueryInterface действительно возвращает S_OK (да) или E_NOINTERFACE (нет). Впрочем, когда QueryInterface возвращает S_OK, то он также возвращает объекту интерфейсный указатель. Для СОМ значение этого указателя чрезвычайно важно, так как оно позволяет клиентам определить, действительно ли на один и тот же объект указывают два интерфейсных указателя.

QueryInterface и IUnknown

Свойство рефлективности QueryInterface гарантирует, что любой интерфейсный указатель сможет удовлетворить запросы на IUnknown, поскольку все интерфейсные указатели неявно принадлежат к типу IUnknown. Спецификация СОМ имеет немного больше ограничений при описании результатов запросов QueryInterface именно на IUnknown. Объект не только должен отвечать «да» на запрос, он должен также возвращать в ответ на каждый запрос в точности одно и то же значение указателя. Это означает, что в следующем коде оба утверждения всегда должны быть верны:

void AssertSameObject(IUnknown *pUnk)

{

IUnknown *pUnk1 = 0,

*pUnk2 = 0;

HRESULT hr1 = pUnk->QueryInterface(IID_IUnknown, (void **)&pUnk1);

HRESULT hr2 = pUnk->QueryInterface(IID_IUnknown, (void **)&pUnk2);

// QueryInterface(IUnknown) must always succeed

// QueryInterface(IUnknown) должно всегда быть успешным

assert(SUCCEEDED(hr1) && SUCCEEDED(hr2));

// two requests for IUnknown must always yield the

// same pointer values

// два запроса на IUnknown должны всегда выдавать

// те же самые значения указателя

assert(pUnk1 == pUnk2);

pUnk1->Release();

pUnk2->Release();

}

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

bool IsSameObject(IUnknown *pUnk1, IUnknown *pUnk2)

{ assert(pUnk1 && pUnk2);

bool bResult = true;

if (pUnk1 != pUnk2)

{

HRESULT hr1, hr2; IUnknown *p1 = 0, *p2 = 0;

hr1 = pUnk1->QueryInterface(IID_IUnknown, (void **)&p1);

assert(SUCCEEDED(hr1));

hr2 = pUnk2->QueryInterface(IID_IUnknown, (void **)&p2);

assert(SUCCEEDED(hr2));

// compare the two pointer values, as these

// represent the identity of the object

// сравниваем значения двух указателей,

// так как они идентифицируют объект

bResult = (р1 == р2); p1->Release();

p2->Release();

}

return bResult;

}

В главе 5 будет рассмотрено, что понятие идентификации является фундаментальным принципом, так как он используется в архитектуре удаленного доступа СОМ с целью эффективно представлять интерфейсные указатели на объекты в сети.

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

class CarBoatPlane : public ICar, public IBoat, public IPlane

{

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

// IBoat methods – методы

IBoat STDMETHODIMP Sink(void);

// IPlahe methods – методы

IPlane STDMETHODIMP TakeOff(void); };

Ниже приведена стандартная реализация QueryInterface в CarBoatPlane:

STDMETHODIMP QueryInterface(REFIID riid, void **ppv)

{

if (riid == IID_IUnknown) *ppv = static_cast(this);

else if (riid == IID_IVehicle) *ppv = static_cast(this);

else if (riid == IID_ICar) *ppv = static_cast(this);

else if (riid == IID_IBoat) *ppv = static_cast(this);

else if (riid == IID_IPlane) *ppv = static_cast(this);

else return (*ppv = 0), E_NOINTERFACE;

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

return S_OK;

}

Для того чтобы быть объектом СОМ, реализация CarBoatPlane QueryInterface должна полностью придерживаться правил IUnknown , приведенных в данной главе.

Класс CarBoatPlane выставляет интерфейсы только типа ICarIPlane, IBoat, IVehicle и IUnknown . Каждая таблица vtbl CarBoatPlane будет ссылаться на единственную реализацию QueryInterface, показанную выше. К каждому поддерживаемому интерфейсу можно обращаться через эту реализацию QueryInterface, так что невозможно найти два несимметричных интерфейса, то есть не существует двух интерфейсов A и B, для которых неверно следующее:

If QI(A)->B Then QI(QI(A)->B)->A

Если следовать той же логике, то поскольку все пять интерфейсов принадлежат к одной и той же реализации QueryInterface, не существует трех интерфейсов А, В и С , для которых неверно следующее:

If QI(QI(A)->B)->C Then QI(A)->C

Наконец, поскольку реализация QueryInterface всегда удовлетворяет запросы на пять возможных интерфейсных указателей, которые могут поддерживаться клиентом, то следующее утверждение должно быть верным для каждого из пяти поддерживаемых интерфейсов:

QI(A)->A

Поскольку из множественного наследования вытекает единственная реализация QueryInterface для всех интерфейсов объекта, в действительности очень трудно нарушить требования симметричности, транзитивности и рефлективности.

Реализация также корректно выполняет правило СОМ об идентификации, возвращая только одно значение указателя при запросе IUnknown:

if (riid == IID_IUnknown) *ppv = static_cast(this);

Если бы реализация QueryInterface возвращала различные указатели vptr для каждого запроса:

if (riid == IID_IUnknown)

{

int n = rand() % 3;

if (n == 0) *ppv = static_cast(this);

else if (n == 1) *ppv = static_cast(this);

else if (n == 2) *ppv = static_cast(this);


Перейти на страницу:
Изменить размер шрифта: