Использование указателей интерфейса СОМ
Программисты 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" опубликованы две статьи, где на примерах
показаны различные подводные камни при использовании интеллектуальных
указателей1. Исходный код, который приводится
в данной книге, содержит интеллектуальный СОМ-указатель, созданный в процессе
написания этих двух статей. В нем делается попытка учесть общие ошибки,
случающиеся как в простых, так и в интеллектуальных указателях СОМ. Класс
интеллектуальных указателей, 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 Эти статьи можно найти на сайтах
http:/www.develop.com/dbox/cxx/InterfacePtr.htm и http://www.develop.com/dbox/cxx/SmartPtr.htm.
Оптимизация QueryInterface
Типы данных
Атрибуты и свойства
Исключения
Где мы находимся?
|