Описание ошибки
Варианты исправления |
В реализации 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 сделать это вместо создания трех(!) описаний багов, понять трудно.