// объект совместим с Pug
pPug->Snore();
pPug->Release();
// R2
}
ICat *pCat = 0;
hr = pUnk->QueryInterface(IID_ICat, (void**)&pCat);
if (SUCCEEDED(hr))
{
// the object is Cat-compatible
// объект совместим с Cat
pCat->IgnoreMaster();
pCat->Release(); // R2
}
}
Хотя имеются очевидные различия в синтаксисе, единственная существенная разница между двумя приведенными фрагментами кода состоит в том, что вариант, основанный на QueryInterface, подчиняется правилам подсчета ссылок СОМ.
Есть несколько тонкостей, связанных с QueryInterface и его употреблением. Метод QueryInterface может возвращать указатели только на тот же самый СОМ-объект, для которого он вызван. Глава 4 посвящена объяснению каждого нюанса этого оператора. Полезно, однако, отметить уже сейчас, что клиент не должен трактовать AddRef и Release как операции с
void BadCOMCode(/*[in]*/ IUnknown *pUnk)
{
ICat *pCat = 0;
IPug *pPug = 0;
HRESULT hr;
hr = pUnk->QueryInterface(IID_ICat, (void**)&pCat);
if (FAILED(hr)) goto cleanup;
hr = pUnk->QueryInterface(IID_IPug, (void**)&pPug);
if (FAILED(hr)) goto cleanup;
pPug->Bark();
pCat->IgnoreMaster();
cleanup:
if (pCat) pUnk->Release();
// pCat got AddRefed in QI
// pCat получил AddRef в QI
if (pPug) pUnk->Release();
// pDog got AddRefed in QI
// pDog получил AddRef в QI
}
Несмотря на то что все три указателя: pCat, pPug и pUnk – указывают на тот же самый объект, клиент не имеет права компенсировать AddRef, который происходит для pCat и pPug при вызове QueryInterface, вызовами Release для pUnk. Правильный вариант этого кода такой:
cleanup:
if (pCat) pCat->Release();
// use AddRefed ptr
// используем указатель AddRef
if (pPug) pPug->Release();
// use AddRefed ptr
// используем указатель AddRef
Здесь Release вызывается для того же
Еще одна тонкость относится ко второму параметру QueryInterface, имеющему тип void**. Весьма забавно то, что QueryInterface, являющийся основой системы типов СОМ, имеет довольно сомнительный в смысле типа аналог в C++:
HRESULT _stdcall QueryInterface(REFIID riid, void** ppv);
Как было отмечено ранее, клиенты вызывают QueryInterface, передавая объекту указатель на интерфейсный указатель в качестве второго параметра вместе с IID, который определяет тип ожидаемого интерфейсного указателя:
IPug *pPug = 0; hr = punk->QueryInterface(IID_IPug, (void**)&pPug);
К сожалению, для компилятора C++ таким же правильным выглядит и следующее:
IPug *pPug = 0; hr = punk->QueryInterface(IID_ICat, (void**)&pPug);
Даже еще более хитроумный вариант компилируется без ошибок:
IPug *pPug = 0; hr = punk->QueryInterface(IID_IPug, (void**)pPug);
Исходя из того, что правила наследования неприменимы к указателям, такое альтернативное определение QueryInterface нe облегчает проблему:
HRESULT QueryInterface(REFIID riid, IUnknown** ppv);
так как неявное приведение типа к родительскому типу (upcasting) применимо только к объектам и указателям на объекты, а не к указателям на указатели на объекты:
IDerived **ppd; IBase **ppb = ppd;
// illegal
// неверно
To же ограничение применимо в равной мере и к ссылкам на указатели. Следующее альтернативное определение вряд ли более удобно для использования клиентами:
HRESULT QueryInterface(const IID& riid, void* ppv);