22 мая 2008

Smart pointer for COM objects

Указатели на объекты должны быть умными - чтобы автоматически управлять временем жизни объекта. Это понятное дело. Для COM объектов есть широкий выбор стандартных умных указателей: _com_ptr_t, CComPtr, CComQIPtr. Какой же из них предпочесть? Большой разницы между ними нет, однако вся соль - в нюансах.

Шаблоны CComPtr и CComQIPtr предлагает нам ATL. Так что ести вы не используете ATL у себя в проекте, то ваш выбор однозначен - _com_ptr_t. Тут даже думать не надо - особого смысла пораждать зависимость от ATL ради CComPtr/CComQIPtr нет.

У _com_ptr_t есть полезный макрос для создания конкретных типов (через typedef): _COM_SMARTPTR_TYPEDEF. Выглядит это так:
// = typedef _com_ptr_t<...> IFooBarPtr;
_COM_SMARTPTR_TYPEDEF(IFooBar, __uuidof(IFooBar));
...
IFooBarPtr FooBar;
Еще бОльшая прелесть состоит в том, что для большинства интерфейсов уже заданы такие typedef-ы, то есть можно смело писать IHTMLDocument2Ptr Document; без соответствующего тайпдефа. Однако засада в том, что сделаны не все тайпдефы и для некоторых интерфейсов придется либо самим объявлять тип через _COM_SMARTPTR_TYPEDEF или использовать _com_ptr_t напрямую.

_com_ptr_t, как и CComQIPtr умеют вызывать QueryInterface "на ходу", что довольно удобно:
IDispatchPtr RawDocument = GetDocument();
IHTMLDocument2Ptr Document(RawDocument); // здесь автоматически вызывается QueryInterface
Небольшим плюсом CComQIPtr может служить его бОльшая лаконичность по сравнению с _com_ptr_t:

CComQIPtr<IHTMLDocument4> Document;
vs
_com_ptr_t<IHTMLDocument4, &__uuidof(IHTMLDocument4)> Document;

Вывод из этого я делаю такой: если используете ATL — берите CComQIPtr, если нет — _com_ptr_t. Если вдруг придется "слезть" с ATL, то будет довольно просто перевести код с CComQIPtr на _com_ptr_t.

Post scriptum
Все перечисленные выше умные указатели переопределяют operator& так, что он возвращает адрес обычного указателя, вместо адреса умного указателя. Это очень удобно для таких вот вызовов, которых в COM предостаточно:
IWebBrowserPtr Browser = GetBrowser();
IDispatchPtr Document;
// calling HRESULT STDMETHODCALLTYPE get_Document(IDispatch **ppDisp);
Browser->get_Document(&Document);
Соответственно, если будет желание положить элемент в контейнер, то оператору & очень желательно вернуть прежнюю логику. Например, обернув умный указатель в CAdapt из ATL:
std::vector< CAdapt< CComQIPtr<IDispatch> > > Objects
А если понадобится, то можно в любой момент сделать "срезку" до CComQIPtr/_com_ptr_t с его переопределенным оператором &:
BOOST_FOREACH(CComQIPtr<IDispatch> &Object, Objects)
Browser->get_Document(&Object);

PPS. Дискуссии по теме: здесь и здесь.

5 комментариев:

CTpaHHoe комментирует...

для CComPtr и CComQIPtr у меня тоже образовалось несколько полезных макросов

#define DEFINE_COMPTR(Intf) typedef CComPtr< Intf > Intf##Ptr;
#define DEFINE_COMQIPTR(Intf, pIID) typedef CComQIPtr< Intf, pIID> Intf##QIPtr;

#define DEFINE_SMART_PTR_IID(Intf, pIID)\
DEFINE_COMPTR(Intf); \
DEFINE_COMQIPTR(Intf, pIID);

#define DEFINE_SMART_PTR(Intf) DEFINE_SMART_PTR_IID(Intf, &IID_##Intf)

используем
DEFINE_SMART_PTR(IPropertyBag);
в коде объявляем

IPropertyBagPtr pPropBag = IPropertyBagQIPtr(pUnknown);

CTpaHHoe комментирует...

и еще вопрос не совсем по теме.
CComPtr это часть ATL, _com_ptr_ это часть Visual Studio. Одного нет в бесплатном варианте (express), другого нет в поставке eclipse.
Есть ли какая-то отдельная библиотека умных указателей для того чтобы без лишних задержек по времени убрать ATL из списка зависимостей или надо снова изобретать велосипед, написав свою реализацию?

Raider комментирует...

CTpaHHoe: Рецепт прост - взять boost::intrusive_ptr<IUnknown> и добавить какую-нибудь обертку для QueryInterface по вкусу. Пример.

Анонимный комментирует...

>Вывод из этого я делаю такой: если >используете ATL — берите >CComQIPtr, если нет — _com_ptr_t.

Com на С++ без ATL - как то смешно звучит :)

CTpaHHoe комментирует...

>> Com на С++ без ATL - как то смешно звучит :)
таковы особенности программирования не под Visual C++