Microsoft KB Archive/288724

= BUG: ATL COM event handler may receive arguments in reverse order =

Article ID: 288724

Article Last Modified on 9/30/2005

-

APPLIES TO


 * Microsoft Visual C++ 6.0 Enterprise Edition
 * Microsoft Visual C++ 6.0 Professional Edition
 * Microsoft Visual C++ 6.0 Standard Edition

-



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 &quot;More Information&quot; 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(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 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 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 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;icFuncs;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; icParams; 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; }                       

<div class="status_section">

STATUS
Microsoft has confirmed that this is a bug in the Microsoft products that are listed in the &quot;Applies to&quot; section.

<div class="moreinformation_section">

Steps to Reproduce Behavior
The following sample creates an event handler class that is derived from IDispEventSimpleImpl and catches the WorkbookBeforeClose event fired by an Excel workbook. The Excel Type Library definition of this event function is: HRESULT WorkbookBeforeClose( struct _Workbook * Wb, VARIANT_BOOL * Cancel);

The main function creates a new Excel instance, adds a workbook, and closes this workbook. This calls your event notification and displays a message box. Then Excel is closed again.  Open Visual C++ and create a new Win32 Console application named Xlevts. Select A simple application.</li>  Paste the following code into Xlevts.cpp. Update the file location of the #import directive depending on your Microsoft Office installation. extern CComModule _Module;
 * 1) include &quot;stdafx.h&quot;
 * 2) include <atlbase.h>
 * 1) include <atlcom.h>

no_namespace rename(&quot;DocumentProperties&quot;,&quot;DocumentPropertiesXL&quot;) rename(&quot;DialogBox&quot;,&quot;DialogBoxXL&quot;) rename(&quot;RGB&quot;,&quot;RGBXL&quot;) \ exclude(&quot;IFont&quot;, &quot;IPicture&quot;)
 * 1) pragma warning (disable:4146)
 * 2) import <C:\\Program Files\\Microsoft Office\\Office\\mso9.dll> \
 * 1) import <C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\VBA6\\vbe6ext.olb> no_namespace
 * 2) import <C:\\Program Files\\Microsoft Office\\Office\\excel9.olb> \

using namespace Excel;

static _ATL_FUNC_INFO WorkbookBeforeClose = {   CC_STDCALL, // Calling convention. VT_EMPTY,  // Return type. 2,             // Number of arguments. { VT_DISPATCH, VT_BOOL|VT_BYREF }  // Argument types. };

class CEventHdl : public IDispEventSimpleImpl<235, CEventHdl, &__uuidof(AppEvents)> { public: BEGIN_SINK_MAP(CEventHdl) SINK_ENTRY_INFO(235,__uuidof(Excel::AppEvents), 0x622, WBBeforeClose, &WorkbookBeforeClose) END_SINK_MAP STDMETHOD(WBBeforeClose)(IDispatch* pBook, VARIANT_BOOL* pbResult) {       *pbResult = VARIANT_FALSE; MessageBox(NULL, _T(&quot;CEventHdl&quot;), _T(&quot;WorkbookBeforeClose called.&quot;), MB_OK); return S_OK; } };

int main(int argc, char* argv[]) {   CoInitialize(NULL);

{       HRESULT hr; // Start Excel _ApplicationPtr app; app.CreateInstance(L&quot;Excel.Application.9&quot;); // Make it visible app->PutVisible(0, VARIANT_TRUE); // Create and connnect event handler... CEventHdl *pHdl = new CEventHdl; pHdl->AddRef; IUnknown *pUnk = NULL; app->QueryInterface(&pUnk); hr = pHdl->DispEventAdvise(pUnk, &__uuidof(AppEvents)); // Add a workbook...          WorkbooksPtr books = app->Workbooks; _WorkbookPtr book1 = books->Add((long)xlWorksheet); // Close workbook book1->PutSaved(0, VARIANT_TRUE); book1->Close; // This line calls our event handler !!! // Finish Excel app->Quit; // Disconnect and release event handler hr = pHdl->DispEventUnadvise(pUnk, &__uuidof(AppEvents)); pUnk->Release; delete pHdl; }   CoUninitialize; return 0; }                   </li> Build a debug application.</li> Put a break point into the WBBeforeClose function of the CEventHdl class.</li> Press F5 to start debugging the application. Wait until the break point is reached.</li> Open the Variables window if it is not already open. (From the View menu, choose Debug Windows, and then choose Variables.) Open the tree in the Variables window to view the pBook and pbResult members. They don't seem to show valid values. Continuing the application doesn't work as expected, Excel isn't closed. Close the applications manually.</li> Copy Atlcom.h to the folder where this project is located and rename it Myatlcom.h. Change the implementation of InvokeFromFuncInfo as described in the &quot;Resolution&quot; section of this article.</li>  At the beginning of this source file Xlevt.cpp, replace the line with: Rebuild the project. </li> Repeat steps 5 and 6.
 * 1) include <atlcom.h>
 * 1) include &quot;myatlcom.h&quot;

This time the variables show correct values and Excel is closed automatically when the application is finished.</li></ol>

<div class="references_section">