BUG: ATL COM event handler may receive arguments in reverse order (288724)
The information in this article applies to:
- Microsoft Visual C++, 32-bit Enterprise Edition 6.0
- Microsoft Visual C++, 32-bit Professional Edition 6.0
- Microsoft Visual C++, 32-bit Learning Edition 6.0
This article was previously published under Q288724 SYMPTOMS
An Active Template Library (ATL) class derived from IDispEventSimpleImpl that handles events from an OLE automation server may fail or exhibit unexpected behavior when an event is processed that has more than one argument. The arguments are in reverse order compared with the type library specification when they are passed to the handler function of this class. Processing the same event from within Microsoft Visual Basic work fine.
The behavior can be reproduced with Microsoft Word or Microsoft Excel (97 or 2000) as OLE automation servers, but also with other, custom servers that pass arguments as named arguments in the DISPPARAMS structure of IDispatch::Invoke when the event is fired.
See the sample code in the "More Information" section of this article for
details.
CAUSE
The ATL implementation of IDispEventSimpleImpl::Invoke() calls
InvokeFromFuncInfo(), which is a helper function, to invoke the event. InvokeFromFuncInfo reorders the arguments of the DISPPARAM structure to be in left-to-right order so that they can be passed to the event handler function in the expected order. There is a problem with the reorder mechanism. The current function implementation assumes that the argument array always contains positional arguments placed right to left within the array. However, the algorithm ignores named arguments. The named arguments are placed before the positional arguments in the argument array with the corresponding dispatch ID placed at the same array position in the array containing the DISPID's for named arguments.
RESOLUTION
To work around this problem, use a version of Atlcom.h in which
IDispEventSimpleImpl::InvokeFromFuncInfo() is corrected. Atlcom.h is located in the Program Files\Microsoft Visual Studio\VC98\ATL\Include folder.
- Replace the implementation of InvokeFromFuncInfo as follows:
HRESULT InvokeFromFuncInfo(void (__stdcall T::*pEvent)(), _ATL_FUNC_INFO& info, DISPPARAMS* pdispparams, VARIANT* pvarResult)
{
ATLASSERT(pdispparams->cArgs == (UINT)info.nParams);
T* pT = static_cast<T*>(this);
VARIANTARG** pVarArgs = info.nParams ? (VARIANTARG**)alloca(sizeof(VARIANTARG*)*info.nParams) : 0;
UINT nIndex = 0;
#ifndef _ATL_IGNORE_NAMED_ARGS
for (nIndex; nIndex < pdispparams->cNamedArgs; nIndex++)
pVarArgs[pdispparams->rgdispidNamedArgs[nIndex]] = &pdispparams->rgvarg[nIndex];
#endif
for (; nIndex < pdispparams->cArgs; nIndex++)
pVarArgs[info.nParams-nIndex-1] = &pdispparams->rgvarg[nIndex];
CComStdCallThunk<T> thunk;
thunk.Init(pEvent, pT);
CComVariant tmpResult;
if (pvarResult == NULL)
pvarResult = &tmpResult;
HRESULT hr = DispCallFunc(
&thunk,
0,
info.cc,
info.vtReturn,
info.nParams,
info.pVarTypes,
pVarArgs,
pvarResult);
ATLASSERT(SUCCEEDED(hr));
return hr;
}
- You must also replace the IDispEventImpl::GetFuncInfoFromID function so that it parses the arguments in forward order as opposed to reverse order. Replace the implementation of IDispEventImpl::GetFuncInfoFromId
() as follows:
HRESULT GetFuncInfoFromId(const IID& /*iid*/, DISPID dispidMember, LCID lcid, _ATL_FUNC_INFO& info)
{
CComPtr<ITypeInfo> spTypeInfo;
if (InlineIsEqualGUID(*_tih.m_plibid, GUID_NULL))
{
_tih.m_plibid = &m_libid;
_tih.m_pguid = &m_iid;
_tih.m_wMajor = m_wMajorVerNum;
_tih.m_wMinor = m_wMinorVerNum;
}
HRESULT hr = _tih.GetTI(lcid, &spTypeInfo);
if (FAILED(hr))
return hr;
CComQIPtr<ITypeInfo2, &IID_ITypeInfo2> spTypeInfo2 = spTypeInfo;
FUNCDESC* pFuncDesc = NULL;
if (spTypeInfo2 != NULL)
{
UINT nIndex;
hr = spTypeInfo2->GetFuncIndexOfMemId(dispidMember, INVOKE_FUNC, &nIndex);
if (FAILED(hr))
return hr;
hr = spTypeInfo->GetFuncDesc(nIndex, &pFuncDesc);
if (FAILED(hr))
return hr;
}
else // search for funcdesc
{
TYPEATTR* pAttr;
hr = spTypeInfo->GetTypeAttr(&pAttr);
if (FAILED(hr))
return hr;
for (int i=0;i<pAttr->cFuncs;i++)
{
hr = spTypeInfo->GetFuncDesc(i, &pFuncDesc);
if (FAILED(hr))
return hr;
if (pFuncDesc->memid == dispidMember)
break;
spTypeInfo->ReleaseFuncDesc(pFuncDesc);
pFuncDesc = NULL;
}
spTypeInfo->ReleaseTypeAttr(pAttr);
if (pFuncDesc == NULL)
return E_FAIL;
}
ATLASSERT(pFuncDesc->cParams <= _ATL_MAX_VARTYPES);
if (pFuncDesc->cParams > _ATL_MAX_VARTYPES)
return E_FAIL;
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] = (VARTYPE)(pFuncDesc->lprgelemdescParam[i].tdesc.lptdesc->vt | VT_BYREF);
if (info.pVarTypes[i] == VT_USERDEFINED)
info.pVarTypes[i] = GetUserDefinedType(spTypeInfo, pFuncDesc->lprgelemdescParam[i].tdesc.hreftype);
}
VARTYPE vtReturn = pFuncDesc->elemdescFunc.tdesc.vt;
switch(vtReturn)
{
case VT_INT:
vtReturn = VT_I4;
break;
case VT_UINT:
vtReturn = VT_UI4;
break;
case VT_VOID:
vtReturn = VT_EMPTY; // this is how DispCallFunc() represents void
break;
case VT_HRESULT:
vtReturn = VT_ERROR;
break;
}
info.vtReturn = vtReturn;
info.cc = pFuncDesc->callconv;
info.nParams = pFuncDesc->cParams;
spTypeInfo->ReleaseFuncDesc(pFuncDesc);
return S_OK;
}
STATUSMicrosoft has confirmed that this is a bug in the Microsoft products that are listed in the "Applies to" section.REFERENCES
The "Passing Parameters" topic on MSDN Online provides more information about how arguments are passed during IDispatch::Invoke:
Modification Type: | Major | Last Reviewed: | 9/30/2005 |
---|
Keywords: | kbAutomation kbBug kbfix KB288724 |
---|
|