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

       

Использование указателей интерфейса СОМ


Программисты C++ должны использовать методы IUnknown явно, потому что перевод модели СОМ на язык C++ не предусматривает использования среды поддержки выполнения (runtime layer) между кодом клиента и кодом объекта. Поэтому IUnknown можно рассматривать просто как набор обещаний, которые все программисты СОМ дают друг другу. Это дает преимущество программистам C++, так как C++ может создавать код, который потенциально более эффективен, чем языки, которые требуют такого динамического слоя при работе с СОМ.

При работе на Visual Basic и Java, в отличие от C++, программисты никогда не видят QueryInterface, AddRef или Release. Для этих двух языков детали IUnknown надежно скрыты за поддерживающей эти языки виртуальной машиной. На Java QueryInterface просто отображается в приведение типа:

public void TryToSnoreAndIgnore(Object obj) { IPug pug; try { pug = (IPug)obj; // VM calls QueryInterface // VM вызывает QueryInterface pug.Snore(); } catch (Throwable ex) { // ignore method or QI failures // игнорируем сбой метода или QI }

ICat cat;

try { cat = (ICat)obj; // VM calls QueryInterface // VM вызывает QueryInterface cat.IgnoreMaster(); } catch (Throwable ex) { // ignore method or QI failures // игнорируется сбой метода или QI } }

Visual Basic не требует от клиентов приведения типов. Вместо этого, когда указатель интерфейса присваивается переменной неподходящего типа, виртуальная машина (VM) Visual Basic молча вызывает QueryInterface от имени клиента:

Sub TryToSnoreAndIgnore(obj as Object) On Error Resume Next ' ignore errors ' игнорируем ошибки Dim pug as IPug Set pug = obj ' VM calls QueryInterface ' VM вызывает QueryInterface If Not (pug is Nothing) Then pug.Snore End if Dim cat as ICat Set cat = obj ' VM calls QueryInterface ' VM вызывает QueryInterface If Not (cat is Nothing) Then cat.IgnoreMaster End if End Sub

Обе виртуальные машины, как Java, так и Visual Basic, выбросят при сбое QueryInterface исключения. В обеих средах виртуальная машина автоматически преобразует языковую концепцию живучести переменной в явные вызовы AddRef и Release, избавляя клиента и от этой подробности.


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

  • Корректно обрабатывать каждый вызов Add/Release во время присваивания.

  • Автоматически уничтожать интерфейс в деструкторе, что снижает возможность утечки ресурса и повышает безопасность (надежность) исключений.

  • Использует систему типов C++ для упрощения вызовов QueryInterface.

  • Прозрачным образом (незаметно для пользователя или программы) замещает необработанные интерфейсные указатели в существующем коде без компрометации правильности программы.

    Последний пункт представляет собой чрезвычайно серьезную проблему. Интернет забит интеллектуальными СОМ-указателями, которые проделывают прозрачную замену обычных указателей, но при этом вводят столько же скрытых ошибок, сколько претендуют устранить. Visual C++ 5.0, например, фактически действует с тремя такими указателями (один на MSC, другой на ATL, а третий для поддержки Direct-to-COM), которые очень просто использовать как правильно, так и неправильно. В сентябрьском 1995 года и в февральском 1996 года выпусках "C++ Report" опубликованы две статьи, где на примерах показаны различные подводные камни при использовании интеллектуальных указателей. Исходный код, который приводится в данной книге, содержит интеллектуальный СОМ-указатель, созданный в процессе написания этих двух статей. В нем делается попытка учесть общие ошибки, случающиеся как в простых, так и в интеллектуальных указателях СОМ. Класс интеллектуальных указателей, SmartInterface, имеет два шаблонных (template) параметра: тип интерфейса в C++ и указатель на соответствующий IID. Все обращения к методам IUnknown скрыты путем перегрузки операторов:

    #include "smartif.h" void TryToSnoreAndIgnore(/* [in] */ IUnknown *pUnk) { // copy constructor calls QueryInterface // конструктор копирования вызывает QueryInterface SmartInterface<IPug, &IID_IPug> pPug = pUnk; if (pPug) // typecast operator returns null-ness // оператор приведения типа возвращает нуль pPug->Snore(); // operator-> returns safe raw ptr // оператор -> возвращает прямой указатель // copy constructor calls QueryInterface // конструктор копирования вызывает QueryInterface SmartInterface<ICat, &IID_ICat> pCat = pUnk; if (pCat) // typecast operator returns null-ness // оператор приведения типа возвращает нуль pCat->IgnoreMaster(); // operator-> returns safe raw ptr // оператор -> возвращает прямой указатель // destructors release held pointers on leaving scope // деструкторы освобождают удерживаемые указатели при // выходе из области действия }

    Интеллектуальные указатели выглядят очень привлекательными на первый взгляд, но могут оказаться очень опасными, так как погружают программиста в дремотное состояние; будто бы ничего страшного, относящегося к СОМ, произойти не может. Интеллектуальные указатели действительно решают реальные проблемы, особенно связанные с исключениями; однако при неосторожном употреблении они могут внести столько же дефектов, сколько они предотвращают. Например, многие интеллектуальные указатели позволяют вызывать любой метод интерфейса через оператор интеллектуального указателя ->. К сожалению, это позволяет клиенту вызывать Release с помощью этого оператора-стрелки без сообщения базовому интеллектуальному указателю о том, что его автоматический вызов Release в его деструкторе теперь является излишним и недопустимым.

    1

    Эти статьи можно найти на сайтах и .


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