Сущность технологии COM

       

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<ICar*>(this); else if (riid == IID_IVehicle) *ppv = static_cast<ICar*>(this); else if (riid == IID_ICar) *ppv = static_cast<ICar*>(this); else if (riid == IID_IBoat) *ppv = static_cast<IBoat*>(this); else if (riid == IID_IPlane) *ppv = static_cast<IPlane*>(this); else return (*ppv = 0), E_NOINTERFACE; ((IUnknown*)*ppv)->AddRef(); return S_OK; }

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

Класс CarBoatPlane выставляет интерфейсы только типа ICar, IPlane, 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<ICar*>(this);

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

if (riid == IID_IUnknown) { int n = rand() % 3; if (n == 0) *ppv = static_cast<ICar*>(this); else if (n == 1) *ppv = static_cast<IBoat*>(this); else if (n == 2) *ppv = static_cast<IPlane*>(this); }

то реализация была бы корректной только в терминах чисто С++-отношений типа (то есть все три интерфейса были бы совместимы по типу с запрошенным типом IUnknown). Эта реализация, однако, не является допустимой с точки зрения СОМ, поскольку правило идентификации для QueryInterface было нарушено.


Содержание раздела