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
было нарушено.
Множественные интерфейсы и имена методов
Динамическая композиция
Двоичная композиция
Включение
Где мы находимся?
|