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. Дискуссии по теме: здесь и здесь.

20 мая 2008

Naming arguments in constructors

Кстати, имена аргументов в конструкторе вполне могут совпадать с именами членов:
class foo
{
foo(int x, int y) : x(x), y(y) {}
int x, y;
};

16 мая 2008

ActiveX controls registration issues

ATL дает довольно удобные средства для создания ActiveX control-ов: стандартный визард вообще весь необходимый код генерирует сам. С проблемами можно столкнуться только при регистрации контролов из под кода, выполняющегося без администраторских прав.

Что можно предпринять в этом случае? На этот случай есть хороший выход - per user component registration: если регистрироваться не в HKEY_CLASSES_ROOT (который на запись мапится в HKEY_LOCAL_MACHINE\SOFTWARE\Classes), а в HKEY_CURRENT_USER\Software\Classes. для этого можно:
а) поправить .rgs файлы, сгенерированные визардом,
б) использовать перенаправление реестра.
Второй вариант хорош тем, что можно это делать по условию "запущены ли мы под админскими правами":
if (!IsAdmin())
{
HKEY hKeyCurrentUser;
RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Classes"), 0, KEY_ALL_ACCESS, &hKeyCurrentUser);
RegOverridePredefKey(HKEY_CLASSES_ROOT, hKeyCurrentUser);
RegCloseKey(hKeyCurrentUser);
}
Облом состоит в том, что в регистрации библиотек типов под Вистой есть ошибка, которая не схватывает перенаправление реестра. Предлагается поставить Service Pack и вызвать хитрую функцию из хитрой библиотеки. Но это не наш путь, требовать всяких сервис паков. В результате регистрацию библиотек типов нужно делать вручную (точнее полу-автоматом - написав .rgs файл), отключив при этом стандартную регистрацию параметром FALSE в выховах _AtlModule.DllRegisterServer() и _AtlModule.DllUnregisterServer().

Но все эти труды будут напрасны, если ваш ActiveX control устанавливается Internet Explorer-ом под Вистой. Он устанавливает компоненты только под админскими правами, то есть если у пользователя их нет - вылезет окно UACа :(