// объект совместим с 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 вызывается для того же интерфейсного указателя, для которого и AddRef (что произошло неявно, когда указатель был возвращен из QueryInterface). Это требование предоставляет разработчику значительную гибкость при реализации объекта. Например, объект может решить подсчитывать ссылки на каждый интерфейс, чтобы активным образом использовать ресурсы, которые обычно используются одним определенным интерфейсом на объект.

Еще одна тонкость относится ко второму параметру 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);

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату