Продолжается подписка на наши издания! Вы не забыли подписаться?

Ошибка в реализации IDispEventImpl<>

В реализации IDispEventImpl имеется ошибка, которая так и не исправлена ни в одном из пакетов исправлений для VS6 (включая SP5). При обработке событий, параметры которых имеют различные типы данных и размеры, функции-обработчики событий получают некорректные значения параметров. Следующий пример будет работать некорректно:

[/*атрибуты пропущены для краткости*/]
dispinterface _ISomeEvents
{
  properties:
  methods:
  [id(1)] HRESULT Event1([in] short s, [in] double d);
};

Причиной является ошибка, допущенная программистами Microsoft в функции IDispEventImpl::GetFuncInfoFromId() (Atlcom.h, строка 3918):

for (int i =0; I < pFuncDesc->cParams; i++)
{
  info.pVarTypes[i] = 
     pFuncDesc->lprgelemdescParam[pFuncDesc->cParams - i - 1].tdesc.vt;
  if (info.pVarTypes[i] == VT_PTR)
     info.pVarTypes[i] = pFuncDesc->lprgelemdescParam[
         pFuncDesc->cParams - i - 1].tdesc.lptdesc->vt | VT_BYREF;
  if (info.pVarTypes[i] == VT_USERDEFINED)
     info.pVarTypes[i] = GetUserDefinedType(spTypeInfo, 
        pFuncDesc->lprgelemdescParam[pFuncDesc->cParams-i-1].tdesc.hreftype);
}

А вот правильный вариант:

for (int i=0; I < pFuncDesc->cParams; i++)
{
  info.pVarTypes[i] = pFuncDesc->lprgelemdescParam[i].tdesc.vt;
  if (info.pVarTypes[i] == VT_PTR)
    info.pVarTypes[i] = pFuncDesc->lprgelemdescParam[
           i].tdesc.lptdesc->vt | VT_BYREF;
  if (info.pVarTypes[i] == VT_USERDEFINED)
    info.pVarTypes[i] = GetUserDefinedType(spTypeInfo, 
         pFuncDesc->lprgelemdescParam[i].tdesc.hreftype);
}

Есть три пути обхода этой проблемы:

Передавать информацию о типах методу-приемнику, используя макрос SINK_ENTRY_INFO() вместо SINK_ENTRY() или SINK_ENTRY_EX() и описать метод в структуре ATL_ FUNC_INFO (вне класса):

static _ATL_FUNC_INFO OnTickInfo = 
{
  CC_STDCALL,   // Соглашение о вызове
  VT_I4,        // Тип возвращаемого значения 
  1,            // Количество аргументов
  {VT_I4}       // Типы аргументов
};

BEGIN_SINK_MAP(CSinkObj)
  SINK_ENTRY_INFO(IDC_SRCOBJ, DIID__EventSink, 1, OnTick, &OnTickInfo)
END_SINK_MAP()

HRESULT __stdcall OnTick(long tickcnt)
{
  ATLTRACE("CSinkObj::OnTick\n");
  return S_OK;
}
...

Пример использования макроса SINK_ENTRY_INFO() также можно найти в MSDN Q194179.

Переопределить виртуальную функцию GetFuncInfoFromId() в своем классе, унаследованном от IDispEventImpl. Новая функция должна быть идентична исходной, за исключением пяти измененных строк кода, приведенных выше.

BEGIN_SINK_MAP(CSinkObj4)
  SINK_ENTRY_EX(IDC_SRCOBJ, DIID__EventSink, 1, OnTick)
  // Эквивалентно:
  // SINK_ENTRY_INFO(IDC_SRCOBJ, DIID__EventSink, 1, OnTick, NULL)
END_SINK_MAP()

HRESULT GetFuncInfoFromId(const IID& iid, DISPID dispidMember,
    LCID lcid, _ATL_FUNC_INFO& info)
{
  if (InlineIsEqualGUID(iid, DIID__EventSink))
  {
    info.cc = CC_STDCALL;
    switch(dispidMember)
    {
    case 1:
      info.vtReturn = VT_I4;
      info.nParams = 1;
      // info.pVarTypes содержит типы параметров
      // параметры хранятся в обратном порядке, с базовым индексом 0
      info.pVarTypes[0] =  VT_I4;
      return S_OK;
    }
  }
  return E_FAIL;
}
...
HRESULT __stdcall OnTick(long tickcnt)
{
  ATLTRACE("CSinkObj::OnTick\n");
  return S_OK;
}
...

Заменить неверный код функции IDispEventImpl::GetFuncInfoFromId() в Atlcom.h. Что помешало Microsoft сделать это вместо создания трех(!) описаний багов, понять трудно.


Copyright © 1994-2016 ООО "К-Пресс"