Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

   Программирование -> C/C++ -> Сущность технологии COM


Приведение типов и IUnknown

В предыдущей главе обсуждалось, почему необходимо определять тип на этапе выполнения в динамически собранной системе. Язык C++ предусматривает разумный механизм для динамического определения типа с помощью оператора dynamic_cast. Хотя эта языковая возможность имеет собственную реализацию для каждого компилятора, в предыдущей главе было предложено средство урегулирования этого - добавление к каждому интерфейсу явного метода, являющегося семантическим эквивалентом dynamic_cast. Ниже приводится IDL-описание QueryInterface:

 HRESULT QueryInterface([in] REFIID riid, [out] void **ppv); 

Первый параметр (riid) является физическим именем запрошенного интерфейса. Второй параметр (ppv) указывает на переменную интерфейсного указателя, которая в случае успешного завершения будет содержать запрошенный указатель на интерфейс.

В ответ на запрос QueryInterface, если объект не поддерживает запрошенный тип интерфейса, он должен возвратить E_NOINTERFACE после установки *ppv в нулевое значение. Если же объект поддерживает запрошенный интерфейс, он должен перезаписать *ppv указателем запрошенного типа и возвратить HRESULT S_OK. Поскольку ppv является [out]-параметром, реализация QueryInterface должна выполнить AddRef для возвращаемого указателя перед тем, как вернуть управление вызывающему объекту (см. в этой главе выше руководящий принцип А2). Этот вызов AddRef должен быть согласован с вызовом Release со стороны клиента. Следующий код показывает динамическое определение типа с использованием оператора C++ dynamic_cast на примере иерархии типов Dog/Cat, описанного ранее в данной главе:

void TryToSnoreAndIgnore(/* [in] */ IUnknown *pUnk) 
{ 
    IPug *pPug = 0;
    pPug = dynamic_cast<IPug*> (pUnk); 
    if (pPug) 
        // the object is Pug-compatible 
        // объект совместим с Pug 
        pPug->Snore(); 
    ICat *pCat = 0; 
    pCat = dynamic_cast<ICat*>(pUnk); 
    if (pCat) 
        // the object is Cat-compatible 
        // объект совместим с Cat pCat->
        IgnoreMaster(); 
} 

Если объект, переданный этой функции, совместим одновременно с ICat и с IDog, то задействованы обе функциональные возможности. Если же объект в действительности не совместим с ICat или с IDog, то данная функция просто проигнорирует пропущенный аспект объекта (или оба аспекта сразу). Ниже показан семантически эквивалентный вариант с использованием QueryInterface:

void TryToSnoreAndIgnore(/* [in] */ IUnknown *pUnk) 
{ 
    HRESULT hr; 
    IPug *pPug = 0; 
    hr = pUnk->QueryInterface(IID_IPug, (void**)&pPug); 
    if (SUCCEEDED(hr)) { 
          // the object is Pug-compatible 
          // объект совместим с 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); 

так как позволяет клиентам отказаться от приведения типа (cast). К сожалению, это решение не уменьшает количества ошибок (обе из предшествующих ошибок все еще возможны), а устраняя необходимость приведения, уничтожает и видимый индикатор того, что устойчивость типов C++ может оказаться в опасности. Если желательна семантика QueryInterface, то выбор типов аргументов, сделанный корпорацией Microsoft, по крайней мере, разумен, если не надежен или изящен. Простейший путь избежать ошибок, связанных c QueryInterface,- это всегда быть уверенным в том, что IID соответствует типу указателя интерфейса, который проходит как второй параметр QueryInterface. На самом деле первый параметр QueryInterface описывает "форму" типа указателя второго параметра. Их связь может быть усилена на этапе компиляции с помощью такого макроса предпроцессора С:

#define IID_PPV_ARG(Type, Expr) IID_##type, \ 
reinterpret_cast<void**>(static_cast<Type **>(Expr)) 

С помощью этого макроса1 компилятор будет уверен в том, что выражение, использованное в приведенном ниже вызове QueryInterface, имеет правильный тип и что используется соответствующий уровень изоляции (indirecton):

IPug *pPug = 0; 
hr = punk->QueryInterface(IID_PPV_ARG(IPug, &pPug)); 

Этот макрос закрывает брешь, вызванную параметром void**, без каких-либо затрат на этапе выполнения.


1Который в значительной мере инспирирован дискуссией между автором и Tye McQueen во время семинара по СОМ.

Реализация IUnknown
Использование указателей интерфейса СОМ
Оптимизация QueryInterface
Типы данных
Атрибуты и свойства
Исключения
Где мы находимся?

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.