DOCUMENT:Q288724 16-NOV-2001 [visualc] TITLE :BUG: ATL COM Event Handler Arguments Received in Reverse Order PRODUCT :Microsoft C Compiler PROD/VER::6.0 OPER/SYS: KEYWORDS:kbATL kbAutomation kbATL300bug kbDSupport kbGrpDSMFCATL ====================================================================== ------------------------------------------------------------------------------- The information in this article applies to: - Microsoft Visual C++, 32-bit Enterprise Edition, version 6.0 - Microsoft Visual C++, 32-bit Professional Edition, version 6.0 - Microsoft Visual C++, 32-bit Learning Edition, version 6.0 ------------------------------------------------------------------------------- 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. 1. 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; } 2. Replace the implementation of 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; } STATUS ====== Microsoft has confirmed this to be a bug in the Microsoft products listed at the beginning of this article. MORE INFORMATION ================ 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. 1. Open Visual C++ and create a new Win32 Console application named "Xlevts" (without the quotation marks). Select A simple application. 2. Paste the following code into Xlevts.cpp. Update the file location of the #import directive depending on your Microsoft Office installation. #include "stdafx.h" #include extern CComModule _Module; #include #pragma warning (disable:4146) #import \ no_namespace rename("DocumentProperties","DocumentPropertiesXL") #import no_namespace #import \ rename("DialogBox","DialogBoxXL") rename("RGB","RGBXL") \ exclude("IFont", "IPicture") 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("CEventHdl"), _T("WorkbookBeforeClose called."), MB_OK); return S_OK; } }; int main(int argc, char* argv[]) { CoInitialize(NULL); { HRESULT hr; // Start Excel _ApplicationPtr app; app.CreateInstance(L"Excel.Application.9"); // 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; } 3. Build a debug application. 4. Put a break point into the WBBeforeClose function of the CEventHdl class. 5. Press F5 to start debugging the application. Wait until the break point is reached. 6. 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. 7. Copy Atlcom.h to the folder where this project is located and rename it "Myatlcom.h" (without the quotation marks). Change the implementation of InvokeFromFuncInfo as described in the "Resolution" section of this article. 8. At the beginning of this source file Xlevt.cpp, replace the line #include with: #include "myatlcom.h" Rebuild the project. 9. Repeat steps 5 and 6. This time the variables show correct values and Excel is closed automatically when the application is finished. REFERENCES ========== The "Passing Parameters" topic on MSDN Online provides more information about how arguments are passed during IDispatch::Invoke: http://msdn.microsoft.com/library/psdk/automat/chap5_3kz7.htm Additional query words: ====================================================================== Keywords : kbATL kbAutomation kbATL300bug kbDSupport kbGrpDSMFCATL Technology : kbVCsearch kbAudDeveloper kbVC600 kbVC32bitSearch Version : :6.0 Issue type : kbbug Solution Type : kbfix ============================================================================= THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY. Copyright Microsoft Corporation 2001.