Как подключиться к событиям СOM-объекта на С++

Автор: Владислав Чистяков
Источник: «Технология Клиент-Сервер»

Часто при использовании COM-объектов в С++-программах встает необходимость подключения к их событиям. Если вы используете MFC, VCL или другие высокоуровневые библиотеки классов, проблем не возникает, так как для решения этой задачи существуют «мастера» и т.п. Но когда такая проблема возникает при работе на уровне API или при использовании ATL, многие программисты робеют, поскольку это связано с написанием больших объемов нетривиального кода, а это неизбежно таит множество подводных камней. В ATL 3.0 господа из Microsoft ввели кусок кода, красиво и лаконично решающий эту проблему. Однако в этот момент отдел маркетинга Microsoft был сильно занят рекламой очень нужных (с его точки зрения) технологий, а на эту мелочь у него у него денег не нашлось. Недавно в конференции microsoft.public.ru.vc был задан вопрос, натолкнувший меня на размышления. Он звучал так: «Как подключиться к connection point'ам на С++ без использования MFC?». Сам вопрос ничего странного не содержит, чего не скажешь об ответах. В них надменно говорилось: «...создаешь disp-интерфейс, делаешь Advise...», и в конце тихонько упоминалось, что Advise можно упростить с помощью вызова AtlAdvise. Стало ясно, что нужно как-то осветить этот вопрос.

Упростить подключение к событиям можно с помощью IDispEventImpl (см. MSDN). Если ваш ActiveX был помещен в ATL-диалог во время разработки, то просто выберите из контекстного меню пункт Events ..., выделите ваш ActiveX и добавляйте обработчики. В случае невизуального ActiveX-элемента придется создать пару строк вручную.

  1. Берете любой класс, реализованный как COM-объект. Главное, чтобы он был унаследован от CComObjectRoot и от CComCoClass. Для создания нового класса проще всего использовать ATL-wizard, в котором выбрать «Simple Object».Проще всего вызвать ATL-wizard и выбрать "Simple Object".
  2. Этот класс нужно унаследовать от IDispEventImpl. Например:
    class ATL_NO_VTABLE CCntrDispEvents :
       public CComObjectRootEx<CComSingleThreadModel>,
       public CComCoClass<CCntrDispEvents>,
       public IDispEventImpl<0, CCntrDispEvents, &DIID__IascContainerCtrlEvents, &LIBID_ASCCONTAINERLib, 1, 0>
    { ...
  3. Добавляете к COM-карте ваш событийный интерфейс:
    BEGIN_COM_MAP(CCntrDispEvents)
      COM_INTERFACE_ENTRY_IID(DIID__IascContainerCtrlEvents, CCntrDispEvents)
    END_COM_MAP()
  4. Добавляете Sink-карту: BEGIN_SINK_MAP(CCntrDispEvents)
    BEGIN_SINK_MAP(CCntrDispEvents)
      SINK_ENTRY_EX(0, DIID__IascContainerCtrlEvents, DISPID_XXX, SomeEventHandler)
    END_SINK_MAP()
    где:
  5. Залезаете в "OLE/COM Object Viewer" (меню Tools), находите описание COM-объекта, к событиям которого нужно подключиться. Находите описание событийного интерфейса и копируете в буфер описание нужного метода.
  6. Возвращаетесь в VC и вставляете скопированное описание внутрь описания класса. В общем, создаете метод с правильными параметрами.
  7. Создаете экземпляр класса (если он еще не создан):
    CComObject<CCntrDispEvents>::CreateInstance(&m_pCntrDispEvents);
    m_pCntrDispEvents->AddRef(); 
    // AddRef нужен, так как у объекта, 
    // созданного таким образом, счетчик равен нулю, и он 
    // самоуничтожится при попытке подключения
  8. Подключаете объект-источник событий:
    m_pCntrDispEvents->DispEventAdvise(pObjectWithEvents);
    ATLASSERT(!hr);

Вот и все. Не забудьте отключить событийный объект:

if(m_pCntrDispEvents)
{
   HRESULT hr = m_pCntrDispEvents->DispEventUnadvise(pObjectWithEvents);
   ATLASSERT(!hr);
}

Чтобы заполучить GUID'ы и другую информацию о типах, можно воспользоваться директивой #import:

#import "xxx.ocx" no_namespace, named_guids, no_implementation,
raw_interfaces_only, raw_dispinterfaces, raw_native_types

Если возникнут проблемы, пишите на audit@optim.su, а если хочется разобраться как подключиться к событиям COM-объекта на низком уровне, прочтите Волшебство ActiveX : Пример ActiveX Control и DCOM, использующих ATL.


Впервые статья была опубликована в журнале <Технология Клиент-Сервер>.
Эту и множество других статей по программированию, разработке БД, многоуровневым технологиям (COM, CORBA, .Net, J2EE) и CASE-средствам вы можете найти на сайте www.optim.su и на страницах журнала.