// XLAutomation.cpp: implementation of the CXLAutomation class. //This is C++ modification of the AutoXL C-sample from //Microsoft Excel97 Developer Kit, Microsoft Press 1997 ////////////////////////////////////////////////////////////////////// #include "stdafx.h" //#include "XLAutomationTester.h" #include "XLAutomation.h" #include #include #include #include #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// /* * Arrays of argument information, which are used to build up the arg list * for an IDispatch call. These arrays are statically allocated to reduce * complexity, but this code could be easily modified to perform dynamic * memory allocation. * * When arguments are added they are placed into these arrays. The * Vargs array contains the argument values, and the lpszArgNames array * contains the name of the arguments, or a NULL if the argument is unnamed. * Flags for the argument such as NOFREEVARIANT are kept in the wFlags array. * * When Invoke is called, the names in the lpszArgNames array are converted * into the DISPIDs expected by the IDispatch::Invoke function. The * IDispatch::GetIDsOfNames function is used to perform the conversion, and * the resulting IDs are placed in the DispIds array. There is an additional * slot in the DispIds and lpszArgNames arrays to allow for the name and DISPID * of the method or property being invoked. * * Because these arrays are static, it is important to call the ClearArgs() * function before setting up arguments. ClearArgs() releases any memory * in use by the argument array and resets the argument counters for a fresh * Invoke. */ //int m_iArgCount; //int m_iNamedArgCount; //VARIANTARG m_aVargs[MAX_DISP_ARGS]; //DISPID m_aDispIds[MAX_DISP_ARGS + 1]; // one extra for the member name //LPOLESTR m_alpszArgNames[MAX_DISP_ARGS + 1]; // used to hold the argnames for GetIDs //WORD m_awFlags[MAX_DISP_ARGS]; ////////////////////////////////////////////////////////////////////// CXLAutomation::CXLAutomation() { m_pdispExcelApp = NULL; m_pdispWorkbook = NULL; m_pdispWorksheet = NULL; m_pdispActiveChart = NULL; InitOLE(); StartExcel(); SetExcelVisible(TRUE); CreateWorkSheet(); //CreateXYChart(); } CXLAutomation::CXLAutomation(BOOL bVisible) { m_pdispExcelApp = NULL; m_pdispWorkbook = NULL; m_pdispWorksheet = NULL; m_pdispActiveChart = NULL; InitOLE(); StartExcel(); SetExcelVisible(bVisible); CreateWorkSheet(); //CreateXYChart(); } CXLAutomation::~CXLAutomation() { //ReleaseExcel(); ReleaseDispatch(); OleUninitialize(); } BOOL CXLAutomation::InitOLE() { DWORD dwOleVer; dwOleVer = CoBuildVersion(); // check the OLE library version if (rmm != HIWORD(dwOleVer)) { MessageBox(NULL, _T("Incorrect version of OLE libraries."), L"Failed", MB_OK | MB_ICONSTOP); return FALSE; } // could also check for minor version, but this application is // not sensitive to the minor version of OLE // initialize OLE, fail application if we can't get OLE to init. if (FAILED(OleInitialize(NULL))) { MessageBox(NULL, _T("Cannot initialize OLE."), L"Failed", MB_OK | MB_ICONSTOP); return FALSE; } return TRUE; } BOOL CXLAutomation::StartExcel() { CLSID clsExcelApp; // if Excel is already running, return with current instance if (m_pdispExcelApp != NULL) return TRUE; /* Obtain the CLSID that identifies EXCEL.APPLICATION * This value is universally unique to Excel versions 5 and up, and * is used by OLE to identify which server to start. We are obtaining * the CLSID from the ProgID. */ if (FAILED(CLSIDFromProgID(L"Excel.Application", &clsExcelApp))) { MessageBox(NULL, _T("Cannot obtain CLSID from ProgID"), L"Failed", MB_OK | MB_ICONSTOP); return FALSE; } // start a new copy of Excel, grab the IDispatch interface if (FAILED(CoCreateInstance(clsExcelApp, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&m_pdispExcelApp))) { MessageBox(NULL, _T("Cannot start an instance of Excel for Automation."), L"Failed", MB_OK | MB_ICONSTOP); return FALSE; } return TRUE; } /******************************************************************* * * INVOKE * *******************************************************************/ /* * INVOKE * * Invokes a method or property. Takes the IDispatch object on which to invoke, * and the name of the method or property as a String. Arguments, if any, * must have been previously setup using the AddArgumentXxx() functions. * * Returns TRUE if the call succeeded. Returns FALSE if an error occurred. * A messagebox will be displayed explaining the error unless the DISP_NOSHOWEXCEPTIONS * flag is specified. Errors can be a result of unrecognized method or property * names, bad argument names, invalid types, or runtime-exceptions defined * by the recipient of the Invoke. * * The argument list is reset via ClearAllArgs() if the DISP_FREEARGS flag is * specified. If not specified, it is up to the caller to call ClearAllArgs(). * * The return value is placed in pvargReturn, which is allocated by the caller. * If no return value is required, pass NULL. It is up to the caller to free * the return value (ReleaseVariant()). * * This function calls IDispatch::GetIDsOfNames for every invoke. This is not * very efficient if the same method or property is invoked multiple times, since * the DISPIDs for a particular method or property will remain the same during * the lifetime of an IDispatch object. Modifications could be made to this code * to cache DISPIDs. If the target application is always the same, a similar * modification is to statically browse and store the DISPIDs at compile-time, since * a given application will return the same DISPIDs in different sessions. * Eliminating the extra cross-process GetIDsOfNames call can result in a * signficant time savings. */ BOOL CXLAutomation::ExlInvoke(IDispatch *pdisp, LPOLESTR szMember, VARIANTARG * pvargReturn, WORD wInvokeAction, WORD wFlags) { HRESULT hr; DISPPARAMS dispparams; unsigned int uiArgErr; EXCEPINFO excep; // Get the IDs for the member and its arguments. GetIDsOfNames expects the // member name as the first name, followed by argument names (if any). m_alpszArgNames[0] = szMember; hr = pdisp->GetIDsOfNames( IID_NULL, m_alpszArgNames, 1 + m_iNamedArgCount, LOCALE_SYSTEM_DEFAULT, m_aDispIds); if (FAILED(hr)) { if (!(wFlags & DISP_NOSHOWEXCEPTIONS)) ShowException(szMember, hr, NULL, 0); return FALSE; } if (pvargReturn != NULL) ClearVariant(pvargReturn); // if doing a property put(ref), we need to adjust the first argument to have a // named arg of DISPID_PROPERTYPUT. if (wInvokeAction & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF)) { m_iNamedArgCount = 1; m_aDispIds[1] = DISPID_PROPERTYPUT; pvargReturn = NULL; } dispparams.rgdispidNamedArgs = m_aDispIds + 1; dispparams.rgvarg = m_aVargs; dispparams.cArgs = m_iArgCount; dispparams.cNamedArgs = m_iNamedArgCount; excep.pfnDeferredFillIn = NULL; hr = pdisp->Invoke(m_aDispIds[0], IID_NULL, LOCALE_SYSTEM_DEFAULT, wInvokeAction, &dispparams, pvargReturn, &excep, &uiArgErr); if (wFlags & DISP_FREEARGS) ClearAllArgs(); if (FAILED(hr)) { // display the exception information if appropriate: if (!(wFlags & DISP_NOSHOWEXCEPTIONS)) ShowException(szMember, hr, &excep, uiArgErr); // free exception structure information SysFreeString(excep.bstrSource); SysFreeString(excep.bstrDescription); SysFreeString(excep.bstrHelpFile); return FALSE; } return TRUE; } /* * ClearVariant * * Zeros a variant structure without regard to current contents */ void CXLAutomation::ClearVariant(VARIANTARG *pvarg) { pvarg->vt = VT_EMPTY; pvarg->wReserved1 = 0; pvarg->wReserved2 = 0; pvarg->wReserved3 = 0; pvarg->lVal = 0; } /* * ClearAllArgs * * Clears the existing contents of the arg array in preparation for * a new invocation. Frees argument memory if so marked. */ void CXLAutomation::ClearAllArgs() { int i; for (i = 0; i < m_iArgCount; i++) { if (m_awFlags[i] & DISPARG_NOFREEVARIANT) // free the variant's contents based on type ClearVariant(&m_aVargs[i]); else ReleaseVariant(&m_aVargs[i]); } m_iArgCount = 0; m_iNamedArgCount = 0; } /* * ReleaseVariant * * Clears a particular variant structure and releases any external objects * or memory contained in the variant. Supports the data types listed above. */ void CXLAutomation::ReleaseVariant(VARIANTARG *pvarg) { VARTYPE vt; VARIANTARG *pvargArray; long lLBound, lUBound, l; vt = pvarg->vt & 0xfff; // mask off flags // check if an array. If so, free its contents, then the array itself. if (V_ISARRAY(pvarg)) { // variant arrays are all this routine currently knows about. Since a // variant can contain anything (even other arrays), call ourselves // recursively. if (vt == VT_VARIANT) { SafeArrayGetLBound(pvarg->parray, 1, &lLBound); SafeArrayGetUBound(pvarg->parray, 1, &lUBound); if (lUBound > lLBound) { lUBound -= lLBound; SafeArrayAccessData(pvarg->parray, (void**)&pvargArray); for (l = 0; l < lUBound; l++) { ReleaseVariant(pvargArray); pvargArray++; } SafeArrayUnaccessData(pvarg->parray); } } else { MessageBox(NULL, _T("ReleaseVariant: Array contains non-variant type"), L"Failed", MB_OK | MB_ICONSTOP); } // Free the array itself. SafeArrayDestroy(pvarg->parray); } else { switch (vt) { case VT_DISPATCH: //(*(pvarg->pdispVal->lpVtbl->Release))(pvarg->pdispVal); pvarg->pdispVal->Release(); break; case VT_BSTR: SysFreeString(pvarg->bstrVal); break; case VT_I2: case VT_BOOL: case VT_R8: case VT_ERROR: // to avoid erroring on an error return from Excel // no work for these types break; default: MessageBox(NULL, _T("ReleaseVariant: Unknown type"), L"Failed", MB_OK | MB_ICONSTOP); break; } } ClearVariant(pvarg); } BOOL CXLAutomation::SetExcelVisible(BOOL bVisible) { if (m_pdispExcelApp == NULL) return FALSE; ClearAllArgs(); AddArgumentBool(NULL, 0, bVisible); return ExlInvoke(m_pdispExcelApp, L"Visible", NULL, DISPATCH_PROPERTYPUT, DISP_FREEARGS); } /******************************************************************* * * ARGUMENT CONSTRUCTOR FUNCTIONS * * Each function adds a single argument of a specific type to the list * of arguments for the current invoke. If appropriate, memory may be * allocated to represent the argument. This memory will be * automatically freed the next time ClearAllArgs() is called unless * the NOFREEVARIANT flag is specified for a particular argument. If * NOFREEVARIANT is specified it is the responsibility of the caller * to free the memory allocated for or contained within the argument. * * Arguments may be named. The name string must be a C-style string * and it is owned by the caller. If dynamically allocated, the caller * must free the name string. * *******************************************************************/ /* * Common code used by all variant types for setting up an argument. */ void CXLAutomation::AddArgumentCommon(LPOLESTR lpszArgName, WORD wFlags, VARTYPE vt) { ClearVariant(&m_aVargs[m_iArgCount]); m_aVargs[m_iArgCount].vt = vt; m_awFlags[m_iArgCount] = wFlags; if (lpszArgName != NULL) { m_alpszArgNames[m_iNamedArgCount + 1] = lpszArgName; m_iNamedArgCount++; } } BOOL CXLAutomation::AddArgumentDispatch(LPOLESTR lpszArgName, WORD wFlags, IDispatch * pdisp) { AddArgumentCommon(lpszArgName, wFlags, VT_DISPATCH); m_aVargs[m_iArgCount++].pdispVal = pdisp; return TRUE; } BOOL CXLAutomation::AddArgumentInt2(LPOLESTR lpszArgName, WORD wFlags, int i) { AddArgumentCommon(lpszArgName, wFlags, VT_I2); m_aVargs[m_iArgCount++].iVal = i; return TRUE; } BOOL CXLAutomation::AddArgumentBool(LPOLESTR lpszArgName, WORD wFlags, BOOL b) { AddArgumentCommon(lpszArgName, wFlags, VT_BOOL); // Note the variant representation of True as -1 m_aVargs[m_iArgCount++].boolVal = b ? -1 : 0; return TRUE; } BOOL CXLAutomation::AddArgumentDouble(LPOLESTR lpszArgName, WORD wFlags, double d) { AddArgumentCommon(lpszArgName, wFlags, VT_R8); m_aVargs[m_iArgCount++].dblVal = d; return TRUE; } BOOL CXLAutomation::ReleaseExcel() { if (m_pdispExcelApp == NULL) return TRUE; // Tell Excel to quit, since for automation simply releasing the IDispatch // object isn't enough to get the server to shut down. // Note that this code will hang if Excel tries to display any message boxes. // This can occur if a document is in need of saving. The CreateChart() code // always clears the dirty bit on the documents it creates, avoiding this problem. ClearAllArgs(); ExlInvoke(m_pdispExcelApp, L"Quit", NULL, DISPATCH_METHOD, 0); // Even though Excel has been told to Quit, we still need to release the // OLE object to account for all memory. ReleaseDispatch(); return TRUE; } //Create an empty workshet BOOL CXLAutomation::CreateWorkSheet() { if(NULL == m_pdispExcelApp) return FALSE; BOOL fResult; VARIANTARG varg1, varg2; IDispatch *pdispRange = NULL; IDispatch *pdispActiveSheet = NULL; IDispatch *pdispActiveCell = NULL; IDispatch *pdispCrt = NULL; // Set wb = [application].Workbooks.Add(template := xlWorksheet) ClearAllArgs(); if (!ExlInvoke(m_pdispExcelApp, L"Workbooks", &varg1, DISPATCH_PROPERTYGET, 0)) return FALSE; ClearAllArgs(); AddArgumentInt2(L"Template", 0, xlWorksheet); fResult = ExlInvoke(varg1.pdispVal, L"Add", &varg2, DISPATCH_METHOD, 0); ReleaseVariant(&varg1); if (!fResult) return FALSE; m_pdispWorkbook = varg2.pdispVal; // Set ws = wb.Worksheets(1) ClearAllArgs(); AddArgumentInt2(NULL, 0, 1); if (!ExlInvoke(m_pdispWorkbook, L"Worksheets", &varg2, DISPATCH_PROPERTYGET, 0)) goto CreateWsBail; m_pdispWorksheet = varg2.pdispVal; fResult = TRUE; CreateWsExit: if (pdispRange != NULL) pdispRange->Release(); if (pdispCrt != NULL) pdispCrt->Release(); return fResult; CreateWsBail: fResult = FALSE; goto CreateWsExit; } /* * OLE and IDispatch use a BSTR as the representation of strings. * This constructor automatically copies the passed-in C-style string * into a BSTR. It is important to not set the NOFREEVARIANT flag * for this function, otherwise the allocated BSTR copy will probably * get lost and cause a memory leak. */ BOOL CXLAutomation::AddArgumentOLEString(LPOLESTR lpszArgName, WORD wFlags, LPOLESTR lpsz) { BSTR b; b = SysAllocString(lpsz); if (!b) return FALSE; AddArgumentCommon(lpszArgName, wFlags, VT_BSTR); m_aVargs[m_iArgCount++].bstrVal = b; return TRUE; } BOOL CXLAutomation::AddArgumentCString(LPOLESTR lpszArgName, WORD wFlags, CString szStr) { BSTR b; b = szStr.AllocSysString(); if (!b) return FALSE; AddArgumentCommon(lpszArgName, wFlags, VT_BSTR); m_aVargs[m_iArgCount++].bstrVal = b; return TRUE; } //Perform Worksheets.Cells(x,y).Value = szStr BOOL CXLAutomation::SetCellsValueToString(double Column, double Row, CString szStr) { if(NULL == m_pdispWorksheet) return FALSE; if(szStr.IsEmpty()) return FALSE; long nBuffSize = szStr.GetLength(); VARIANTARG vargRng; ClearAllArgs(); AddArgumentDouble(NULL, 0, Column); AddArgumentDouble(NULL, 0, Row); if(!ExlInvoke(m_pdispWorksheet, L"Cells",&vargRng, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; AddArgumentCString(NULL, 0, szStr ); if (!ExlInvoke(vargRng.pdispVal, L"Value", NULL, DISPATCH_PROPERTYPUT, 0)) return FALSE; ReleaseVariant(&vargRng); return TRUE; } //Create XY chart. Y values are in column nYColumn. BOOL CXLAutomation::CreateXYChart(int nYColumn) { if(NULL == m_pdispWorksheet) return FALSE; BOOL fResult; VARIANTARG varg1, varg2; IDispatch *pdispRange = NULL; IDispatch *pdispCrt = NULL; // set sourceRange = ws.Columns(nYColumn) ClearAllArgs(); AddArgumentDouble(NULL, 0, nYColumn); if (!ExlInvoke(m_pdispWorksheet, L"Columns", &varg2, DISPATCH_PROPERTYGET, DISP_FREEARGS)) goto CreateChartBail; pdispRange = varg2.pdispVal; // set crt = wb.Charts.Add ClearAllArgs(); if (!ExlInvoke(m_pdispWorkbook, L"Charts", &varg1, DISPATCH_PROPERTYGET, 0)) goto CreateChartBail; ClearAllArgs(); fResult = ExlInvoke(varg1.pdispVal, L"Add", &varg2, DISPATCH_METHOD, 0); ReleaseVariant(&varg1); if (!fResult) goto CreateChartBail; pdispCrt = varg2.pdispVal; ClearAllArgs(); if (!ExlInvoke(m_pdispWorkbook, L"ActiveChart", &varg1, DISPATCH_PROPERTYGET, 0)) goto CreateChartBail; m_pdispActiveChart = varg1.pdispVal; ClearAllArgs(); AddArgumentInt2(NULL, 0, xlXYScatterLinesNoMarkers); if (!ExlInvoke(m_pdispActiveChart, L"ChartType", &varg1, DISPATCH_PROPERTYPUT, 0)) goto CreateChartBail; //Charts.Add //ActiveChart.ChartType = xlXYScatterLinesNoMarkers //ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("A:A, B:B"), PlotBy:= _ // xlColumns ClearAllArgs(); AddArgumentInt2(L"PlotBy", 0, xlColumns); AddArgumentDispatch(L"Source", 0, pdispRange); // will auto-free pdispRange = NULL; if (!ExlInvoke(m_pdispActiveChart, L"SetSourceData", NULL, DISPATCH_METHOD, DISP_FREEARGS)) goto CreateChartBail; fResult = TRUE; CreateChartExit: if (pdispRange != NULL) pdispRange->Release(); if (pdispCrt != NULL) pdispCrt->Release(); return fResult; CreateChartBail: fResult = FALSE; goto CreateChartExit; return TRUE; } BOOL CXLAutomation::SetRangeValueDouble(LPOLESTR lpszRef, double d) { if(NULL == m_pdispWorksheet) return FALSE; VARIANTARG vargRng; BOOL fResult; ClearAllArgs(); AddArgumentOLEString(NULL, 0, lpszRef); if (!ExlInvoke(m_pdispWorksheet, L"Range", &vargRng, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; AddArgumentDouble(NULL, 0, d); fResult = ExlInvoke(vargRng.pdispVal, L"Value", NULL, DISPATCH_PROPERTYPUT, 0); ReleaseVariant(&vargRng); return fResult; } /* * Constructs an 1-dimensional array containing variant strings. The strings * are copied from an incoming array of C-Strings. */ BOOL CXLAutomation::AddArgumentCStringArray(LPOLESTR lpszArgName, WORD wFlags, LPOLESTR *paszStrings, int iCount) { SAFEARRAY *psa; SAFEARRAYBOUND saBound; VARIANTARG *pvargBase; VARIANTARG *pvarg; int i, j; saBound.lLbound = 0; saBound.cElements = iCount; psa = SafeArrayCreate(VT_VARIANT, 1, &saBound); if (psa == NULL) return FALSE; SafeArrayAccessData(psa, (void**) &pvargBase); pvarg = pvargBase; for (i = 0; i < iCount; i++) { // copy each string in the list of strings ClearVariant(pvarg); pvarg->vt = VT_BSTR; if ((pvarg->bstrVal = SysAllocString(*paszStrings++)) == NULL) { // memory failure: back out and free strings alloc'ed up to // now, and then the array itself. pvarg = pvargBase; for (j = 0; j < i; j++) { SysFreeString(pvarg->bstrVal); pvarg++; } SafeArrayDestroy(psa); return FALSE; } pvarg++; } SafeArrayUnaccessData(psa); // With all memory allocated, setup this argument AddArgumentCommon(lpszArgName, wFlags, VT_VARIANT | VT_ARRAY); m_aVargs[m_iArgCount++].parray = psa; return TRUE; } //May be new points have been added to the plot data source. Update plot range BOOL CXLAutomation::UpdatePlotRange(int nYColumn) { if((NULL == m_pdispWorksheet) || (NULL == m_pdispWorkbook) || (NULL == m_pdispActiveChart)) return FALSE; VARIANTARG varg1; IDispatch *pdispRange = NULL; IDispatch *pdispActiveChart = NULL; BOOL bResult = TRUE; ClearAllArgs(); AddArgumentDouble(NULL, 0, nYColumn); if (!ExlInvoke(m_pdispWorksheet, L"Columns", &varg1, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; pdispRange = varg1.pdispVal; //ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("A:A, B:B"), PlotBy:= _ // xlColumns ClearAllArgs(); AddArgumentInt2(L"PlotBy", 0, xlColumns); AddArgumentDispatch(L"Source", 0, pdispRange); // will auto-free pdispRange = NULL; if (!ExlInvoke(m_pdispActiveChart, L"SetSourceData", NULL, DISPATCH_METHOD, DISP_FREEARGS)) bResult = FALSE; ClearAllArgs(); if(NULL != pdispRange) { pdispRange->Release(); pdispRange = NULL; } return bResult; } //Copy string to clipboard and paste it to worksheet BOOL CXLAutomation::PasteStringToWorksheet(CString pDataBuffer) { if(NULL == m_pdispWorksheet) return FALSE; if(pDataBuffer.IsEmpty()) return FALSE; long nBuffSize = pDataBuffer.GetLength(); //Nothing to copy if(0 == nBuffSize) return FALSE; HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, 24); if (OpenClipboard(NULL) ) { HGLOBAL hClipMem; char* buffer; BOOL bResult = TRUE; EmptyClipboard(); hClipMem = GlobalAlloc(GMEM_DDESHARE, nBuffSize); buffer = (char*)GlobalLock(hClipMem); memcpy(buffer, pDataBuffer, nBuffSize); GlobalUnlock(hClipMem); SetClipboardData(CF_TEXT, hClipMem); CloseClipboard(); //Paste data from clipboard // set sourceRange = ws.Range("A1") IDispatch* pdispRange = NULL; VARIANTARG varg1, varg2; ClearAllArgs(); AddArgumentOLEString(NULL, 0, L"A1"); if (!ExlInvoke(m_pdispWorksheet, L"Range", &varg2, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; pdispRange = varg2.pdispVal; //Force past from clipboard //set as = sourceRange.Select ClearAllArgs(); if (!ExlInvoke(pdispRange, L"Select", &varg2, DISPATCH_PROPERTYGET, DISP_FREEARGS)) goto Failed; // set wb.ActiveSheet.Paste ClearAllArgs(); if (!ExlInvoke(m_pdispWorkbook, L"ActiveSheet", &varg1, DISPATCH_PROPERTYGET, 0)) goto Failed; ClearAllArgs(); // set wb.ActiveSheet.Paste ClearAllArgs(); if (!ExlInvoke(m_pdispWorkbook, L"ActiveSheet", &varg1, DISPATCH_PROPERTYGET, 0)) goto Failed; ClearAllArgs(); bResult = ExlInvoke(varg1.pdispVal, L"Paste", &varg2, DISPATCH_METHOD, 0); ReleaseVariant(&varg1); goto Success; Failed: bResult = FALSE; Success: if(NULL != pdispRange) { pdispRange->Release(); pdispRange = NULL; } return bResult; } return FALSE; } //Clean up: release dipatches void CXLAutomation::ReleaseDispatch() { if(NULL != m_pdispExcelApp) { m_pdispExcelApp->Release(); m_pdispExcelApp = NULL; } if(NULL != m_pdispWorksheet) { m_pdispWorksheet->Release(); m_pdispWorksheet = NULL; } if(NULL != m_pdispWorkbook) { m_pdispWorkbook->Release(); m_pdispWorkbook = NULL; } if(NULL != m_pdispActiveChart) { m_pdispActiveChart->Release(); m_pdispActiveChart = NULL; } } void CXLAutomation::ShowException(LPOLESTR szMember, HRESULT hr, EXCEPINFO *pexcep, unsigned int uiArgErr) { TCHAR szBuf[512]; switch (GetScode(hr)) { case DISP_E_UNKNOWNNAME: wsprintf(szBuf, L"%s: Unknown name or named argument.", szMember); break; case DISP_E_BADPARAMCOUNT: wsprintf(szBuf, L"%s: Incorrect number of arguments.", szMember); break; case DISP_E_EXCEPTION: wsprintf(szBuf, L"%s: Error %d: ", szMember, pexcep->wCode); if (pexcep->bstrDescription != NULL) lstrcat(szBuf, (TCHAR*)pexcep->bstrDescription); else lstrcat(szBuf, L"<>"); break; case DISP_E_MEMBERNOTFOUND: wsprintf(szBuf, L"%s: method or property not found.", szMember); break; case DISP_E_OVERFLOW: wsprintf(szBuf, L"%s: Overflow while coercing argument values.", szMember); break; case DISP_E_NONAMEDARGS: wsprintf(szBuf, L"%s: Object implementation does not support named arguments.", szMember); break; case DISP_E_UNKNOWNLCID: wsprintf(szBuf, L"%s: The locale ID is unknown.", szMember); break; case DISP_E_PARAMNOTOPTIONAL: wsprintf(szBuf, L"%s: Missing a required parameter.", szMember); break; case DISP_E_PARAMNOTFOUND: wsprintf(szBuf, L"%s: Argument not found, argument %d.", szMember, uiArgErr); break; case DISP_E_TYPEMISMATCH: wsprintf(szBuf, L"%s: Type mismatch, argument %d.", szMember, uiArgErr); break; default: wsprintf(szBuf, L"%s: Unknown error occured.", szMember); break; } MessageBox(NULL, szBuf, L"OLE Error", MB_OK | MB_ICONSTOP); } //Delete entire line from the current worksheet //Worksheet.Rows(nLine).Select //Selection.Delete Shift:=xlUp BOOL CXLAutomation::DeleteRow(long nRow) { if(NULL == m_pdispWorksheet) return FALSE; VARIANTARG varg1; ClearAllArgs(); AddArgumentDouble(NULL, 0, nRow); if (!ExlInvoke(m_pdispWorksheet, L"Rows", &varg1, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); AddArgumentInt2(L"Shift", 0, xlUp); if (!ExlInvoke(varg1.pdispVal, L"Delete", NULL, DISPATCH_METHOD, DISP_FREEARGS)) return FALSE; return TRUE; } //Save current workbook as an Excel file: //ActiveWorkbook.SaveAs //FileName:=szFileName, FileFormat:=xlNormal, //Password:=szPassword, //WriteResPassword:=szWritePassword, //ReadOnlyRecommended:= bReadOnly, //CreateBackup:= bBackup BOOL CXLAutomation::SaveAs(CString szFileName, int nFileFormat, CString szPassword, CString szWritePassword, BOOL bReadOnly, BOOL bBackUp) { if(NULL == m_pdispWorkbook) return FALSE; ClearAllArgs(); AddArgumentBool(L"CreateBackup", 0, bBackUp); AddArgumentBool(L"ReadOnlyRecommended", 0, bReadOnly); AddArgumentCString(L"WriteResPassword", 0, szWritePassword); AddArgumentCString(L"Password", 0, szPassword); AddArgumentCString(L"FileName", 0, szFileName); if (!ExlInvoke(m_pdispWorkbook, L"SaveAs", NULL, DISPATCH_METHOD, DISP_FREEARGS)) return FALSE; return TRUE; } //Get Worksheet.Calls(nColumn, nRow).Value //This method is not fully tested - see code coments CString CXLAutomation::GetCellValueCString(int nColumn, int nRow) { CString szValue =_T(""); if(NULL == m_pdispWorksheet) return szValue; VARIANTARG vargRng, vargValue; ClearAllArgs(); AddArgumentDouble(NULL, 0, nColumn); AddArgumentDouble(NULL, 0, nRow); if(!ExlInvoke(m_pdispWorksheet, L"Cells",&vargRng, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return szValue; if (!ExlInvoke(vargRng.pdispVal, L"Value", &vargValue, DISPATCH_PROPERTYGET, 0)) return szValue; VARTYPE Type = vargValue.vt; switch (Type) { case VT_UI1: { unsigned char nChr = vargValue.bVal; //szValue = nChr; szValue.Format(L"%d",nChr);// = nChr; } break; case VT_I4: { long nVal = vargValue.lVal; szValue.Format(L"%i", nVal); } break; case VT_R4: { float fVal = vargValue.fltVal; szValue.Format(L"%f", fVal); } break; case VT_R8: { double dVal = vargValue.dblVal; szValue.Format(L"%f", dVal); } break; case VT_BSTR: { BSTR b = vargValue.bstrVal; szValue = b; } break; case VT_BYREF|VT_UI1: { //Not tested unsigned char* pChr = vargValue.pbVal; // szValue = *pChr; szValue.Format(L"%f", *pChr); } break; case VT_BYREF|VT_BSTR: { //Not tested BSTR* pb = vargValue.pbstrVal; szValue = *pb; } case 0: { //Empty szValue = _T(""); } break; } // ReleaseVariant(&vargRng); // ReleaseVariant(&vargValue); return szValue; } //Insert picture from file szFileName to worksheet //The left top corner of the picture is position in (Column, nRow) //on the worksheet //Size of the picture in % of original size is given by dPicWidth, dPicHeight //If dPicWidth = 0.0, dPicHeight = 0.0 or dPicWidth = 1.0, dPicHeight = 1.0 //the picture has default (i.e., original) size BOOL CXLAutomation::InsertPictureToWorksheet(CString szFileName, int Column, int Row, double dPicWidth, double dPicHeight) { //No file name provided. Leave. if(szFileName.IsEmpty()) return FALSE; if(NULL == m_pdispWorksheet) return FALSE; //First, select cell where you want copy the picture (i.e., the top left corner of the picture //Leave if Column and Row are outside the worksheet if((Column < 1) || (Row < 1)) return FALSE; VARIANTARG vargRng, vargActiveCell; VARIANTARG varg1, varg2; ClearAllArgs(); AddArgumentDouble(NULL, 0, Column); AddArgumentDouble(NULL, 0, Row); if(!ExlInvoke(m_pdispWorksheet, L"Cells",&vargRng, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); if(!ExlInvoke(vargRng.pdispVal, L"Select",&vargActiveCell, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ReleaseVariant(&vargRng); ReleaseVariant(&vargActiveCell); //Run this macros to incert picture from file //ActiveSheet.Pictures.Insert("c:\mypicture.bmp").Select //or //ActiveSheet.Pictures.Insert ("C:\mypicture.bmp") //ActiveSheet.Pictures.ShapeRange.ScaleWidth 0.31, msoScaleFromTopLeft ClearAllArgs(); if (!ExlInvoke(m_pdispWorksheet, L"Pictures", &varg1, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); AddArgumentCString(NULL, 0, szFileName); if (!ExlInvoke(varg1.pdispVal, L"Insert", &varg2, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; if((dPicWidth != 0.0) && (dPicHeight != 0.0)) { //Run this macros to resize the picture: //Selection.ShapeRange.ScaleWidth dPicWidth, msoFalse, msoScaleFromTopLeft //Selection.ShapeRange.ScaleHeight dPicWidth, msoFalse, msoScaleFromTopLeft VARIANTARG vargImage; ClearAllArgs(); if (!ExlInvoke(varg1.pdispVal, L"ShapeRange", &vargImage, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); //msoScaleFromTopLeft = 0 - this argument indicate scaling from top left AddArgumentInt2(NULL, 0, 0); AddArgumentDouble(NULL, 0, dPicWidth); if (!ExlInvoke(vargImage.pdispVal, L"ScaleWidth", NULL, DISPATCH_METHOD, 0)) //DISP_FREEARGS)) return FALSE; if (!ExlInvoke(vargImage.pdispVal, L"ScaleHeight", NULL, DISPATCH_METHOD, 0)) //DISP_FREEARGS)) return FALSE; ClearAllArgs(); ReleaseVariant(&vargImage); } ReleaseVariant(&varg1); ReleaseVariant(&varg2); return TRUE; } //Paste image to clipborad BOOL CXLAutomation::PlaceImageToClipboard(BYTE *pImage) { if(NULL == pImage) return FALSE; LPBITMAPINFOHEADER pBmpHeader = (LPBITMAPINFOHEADER) pImage; long nImageBufferSize; //Find the size of buffer to store this image long nBitCount = pBmpHeader->biBitCount; long nWidth = pBmpHeader->biWidth; long nHeight = pBmpHeader->biHeight; //We will support only 8-bit and 24-bit images //8-bit image has 4x256 = 1024-byte palette table followed after the bitmapinfoheader //24-bit image does not have the palette table //When calculating buffer size remember that the BMP image is 32-bit alligned if((nBitCount != 8) && (nBitCount != 24)) return FALSE; //calculate number of bytes in one image line long nBytePerLine = (nBitCount / 8) * nWidth; //Here, for instance 72-bit line should be rounded to 96-bit to be 32-bit aligned nBytePerLine = 4 * ((nBytePerLine + 3) / 4); nImageBufferSize = sizeof(BITMAPINFOHEADER) + (nBytePerLine * nHeight); //if image is 8-bit, add the size of palette table if(8 == nBitCount) nImageBufferSize = nImageBufferSize + 256 * sizeof(RGBQUAD); //Place image to clipboard if (OpenClipboard(NULL) ) { HGLOBAL hClipMem; BYTE* buffer; BOOL bResult = TRUE; EmptyClipboard(); hClipMem = GlobalAlloc(GMEM_DDESHARE, nImageBufferSize); //hClipMem = GlobalAlloc(GMEM_MOVEABLE, nImageBufferSize); buffer = (BYTE*)GlobalLock(hClipMem); memcpy(buffer, pImage, nImageBufferSize); SetClipboardData(CF_DIB, hClipMem); GlobalUnlock(hClipMem); CloseClipboard(); return TRUE; } return FALSE; } //Insert image to worksheet using clipboard BOOL CXLAutomation::InsertPictureToWorksheet(BYTE *pImage, int Column, int Row, double dPicWidth, double dPicHeight) { //Place image to clipboard if(NULL != pImage) if(!PlaceImageToClipboard(pImage)) return FALSE; //Select cell where you want copy the picture (i.e., the top left corner of the picture //Leave if Column and Row are outside the worksheet if((Column < 1) || (Row < 1)) return FALSE; VARIANTARG vargCell, vargActiveCell; VARIANTARG vargActiveSelection; ClearAllArgs(); AddArgumentDouble(NULL, 0, Column); AddArgumentDouble(NULL, 0, Row); if(!ExlInvoke(m_pdispWorksheet, L"Cells",&vargCell, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); if(!ExlInvoke(vargCell.pdispVal, L"Select",&vargActiveCell, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ReleaseVariant(&vargCell); ReleaseVariant(&vargActiveCell); //Paste image from clipboard //by runing: //ActiveSheet.Paste ClearAllArgs(); if (!ExlInvoke(m_pdispWorksheet, L"Paste", &vargActiveSelection, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ReleaseVariant(&vargActiveSelection); //Select image if (!ExlInvoke(m_pdispWorksheet, L"Pictures", &vargActiveSelection, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; //Resize image if((dPicWidth != 0.0) && (dPicHeight != 0.0)) { //Run this macros to resize the picture: //Selection.ShapeRange.ScaleWidth dPicWidth, msoFalse, msoScaleFromTopLeft //Selection.ShapeRange.ScaleHeight dPicWidth, msoFalse, msoScaleFromTopLeft VARIANTARG vargImage; ClearAllArgs(); if (!ExlInvoke(vargActiveSelection.pdispVal, L"ShapeRange", &vargImage, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; ClearAllArgs(); //msoScaleFromTopLeft = 0 - this argument indicate scaling from top left AddArgumentInt2(NULL, 0, 0); AddArgumentDouble(NULL, 0, dPicWidth); if (!ExlInvoke(vargImage.pdispVal, L"ScaleWidth", NULL, DISPATCH_METHOD, 0)) //DISP_FREEARGS)) return FALSE; if (!ExlInvoke(vargImage.pdispVal, L"ScaleHeight", NULL, DISPATCH_METHOD, 0)) //DISP_FREEARGS)) return FALSE; ClearAllArgs(); ReleaseVariant(&vargImage); } ReleaseVariant(&vargActiveSelection); return TRUE; } //Open Microsoft Excel file and switch to the firs available worksheet. BOOL CXLAutomation::OpenExcelFile(CString szFileName) { //Leave if the file cannot be open if(NULL == m_pdispExcelApp) return FALSE; if(szFileName.IsEmpty()) return FALSE; VARIANTARG varg1, vargWorkbook, vargWorksheet; ClearAllArgs(); if (!ExlInvoke(m_pdispExcelApp, L"Workbooks", &varg1, DISPATCH_PROPERTYGET, 0)) return FALSE; ClearAllArgs(); AddArgumentCString(L"Filename", 0, szFileName); if (!ExlInvoke(varg1.pdispVal, L"Open", &vargWorkbook, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; //Now let's get the first worksheet of this workbook ClearAllArgs(); AddArgumentInt2(NULL, 0, 1); if (!ExlInvoke(vargWorkbook.pdispVal, L"Worksheets", &vargWorksheet, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; //Close the empty worksheet ClearAllArgs(); if (!ExlInvoke(m_pdispWorkbook, L"Close", NULL, DISPATCH_PROPERTYGET, DISP_FREEARGS)) return FALSE; //Remember the newly open worksheet m_pdispWorkbook = vargWorkbook.pdispVal; m_pdispWorksheet = vargWorksheet.pdispVal; return TRUE; }