如何在不使用 MFC 或 #import 的情況下,從 C++ 將 Excel 自動化
摘要
以直接 C++ 撰寫自動化程式碼有幾個優點。 首先,您可執行您想要的動作。 接下來,您的程式碼會更小、更快且更容易偵錯。 最後,您不會相依于任何程式庫。 即使您致力於使用 MFC 的包裝函式類別或 Visual C++ 的原生 COM 支援 (#import) ,您可能仍然必須深入探討 IDispatch 和 COM 自動化的記憶體,才能解決這些架構的常見 Bug 和限制。
其他相關資訊
請遵循下列步驟來建置簡單的 Visual C++ 6.0 主控台應用程式,僅使用 C++ 將 Microsoft Office Excel 自動化:
- 啟動 Visual C++ 6.0,然後建立名為 XlCpp 的新 Win32 主控台應用程式。 選擇 「Hello, World!」 應用程式基底,然後按一下 [ 完成]。
- 開啟產生的 XlCpp.cpp,然後在 main () 函式之前新增下列程式碼。
#include <ole2.h> // OLE2 Definitions // AutoWrap() - Automation helper function... HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...) { // Begin variable-argument list... va_list marker; va_start(marker, cArgs); if(!pDisp) { MessageBox(NULL, "NULL IDispatch passed to AutoWrap()", "Error", 0x10010); _exit(0); } // Variables used... DISPPARAMS dp = { NULL, NULL, 0, 0 }; DISPID dispidNamed = DISPID_PROPERTYPUT; DISPID dispID; HRESULT hr; char buf[200]; char szName[200]; // Convert down to ANSI WideCharToMultiByte(CP_ACP, 0, ptName, -1, szName, 256, NULL, NULL); // Get DISPID for name passed... hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID); if(FAILED(hr)) { sprintf(buf, "IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx", szName, hr); MessageBox(NULL, buf, "AutoWrap()", 0x10010); _exit(0); return hr; } // Allocate memory for arguments... VARIANT *pArgs = new VARIANT[cArgs+1]; // Extract arguments... for(int i=0; i<cArgs; i++) { pArgs[i] = va_arg(marker, VARIANT); } // Build DISPPARAMS dp.cArgs = cArgs; dp.rgvarg = pArgs; // Handle special-case for property-puts! if(autoType & DISPATCH_PROPERTYPUT) { dp.cNamedArgs = 1; dp.rgdispidNamedArgs = &dispidNamed; } // Make the call! hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL); if(FAILED(hr)) { sprintf(buf, "IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx", szName, dispID, hr); MessageBox(NULL, buf, "AutoWrap()", 0x10010); _exit(0); return hr; } // End variable-argument section... va_end(marker); delete [] pArgs; return hr; }
- 在 main () 函式內,以下列程式碼取代 printf () 行。
// Initialize COM for this thread... CoInitialize(NULL); // Get CLSID for our server... CLSID clsid; HRESULT hr = CLSIDFromProgID(L"Excel.Application", &clsid); if(FAILED(hr)) { ::MessageBox(NULL, "CLSIDFromProgID() failed", "Error", 0x10010); return -1; } // Start server and get IDispatch... IDispatch *pXlApp; hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pXlApp); if(FAILED(hr)) { ::MessageBox(NULL, "Excel not registered properly", "Error", 0x10010); return -2; } // Make it visible (i.e. app.visible = 1) { VARIANT x; x.vt = VT_I4; x.lVal = 1; AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, x); } // Get Workbooks collection IDispatch *pXlBooks; { VARIANT result; VariantInit(&result); AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"Workbooks", 0); pXlBooks = result.pdispVal; } // Call Workbooks.Add() to get a new workbook... IDispatch *pXlBook; { VARIANT result; VariantInit(&result); AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"Add", 0); pXlBook = result.pdispVal; } // Create a 15x15 safearray of variants... VARIANT arr; arr.vt = VT_ARRAY | VT_VARIANT; { SAFEARRAYBOUND sab[2]; sab[0].lLbound = 1; sab[0].cElements = 15; sab[1].lLbound = 1; sab[1].cElements = 15; arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab); } // Fill safearray with some values... for(int i=1; i<=15; i++) { for(int j=1; j<=15; j++) { // Create entry value for (i,j) VARIANT tmp; tmp.vt = VT_I4; tmp.lVal = i*j; // Add to safearray... long indices[] = {i,j}; SafeArrayPutElement(arr.parray, indices, (void *)&tmp); } } // Get ActiveSheet object IDispatch *pXlSheet; { VARIANT result; VariantInit(&result); AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"ActiveSheet", 0); pXlSheet = result.pdispVal; } // Get Range object for the Range A1:O15... IDispatch *pXlRange; { VARIANT parm; parm.vt = VT_BSTR; parm.bstrVal = ::SysAllocString(L"A1:O15"); VARIANT result; VariantInit(&result); AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm); VariantClear(&parm); pXlRange = result.pdispVal; } // Set range with our safearray... AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"Value", 1, arr); // Wait for user... ::MessageBox(NULL, "All done.", "Notice", 0x10000); // Set .Saved property of workbook to TRUE so we aren't prompted // to save when we tell Excel to quit... { VARIANT x; x.vt = VT_I4; x.lVal = 1; AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlBook, L"Saved", 1, x); } // Tell Excel to quit (i.e. App.Quit) AutoWrap(DISPATCH_METHOD, NULL, pXlApp, L"Quit", 0); // Release references... pXlRange->Release(); pXlSheet->Release(); pXlBook->Release(); pXlBooks->Release(); pXlApp->Release(); VariantClear(&arr); // Uninitialize COM for this thread... CoUninitialize();
- 編譯並執行。
AutoWrap () 函式可簡化與直接使用 IDispatch 相關的大部分低階詳細資料。 您可以隨意在自己的實作中使用它。 其中一個注意事項是,如果您傳遞多個參數,則必須以反向順序傳遞。 例如:
VARIANT parm[3];
parm[0].vt = VT_I4; parm[0].lVal = 1;
parm[1].vt = VT_I4; parm[1].lVal = 2;
parm[2].vt = VT_I4; parm[2].lVal = 3;
AutoWrap(DISPATCH_METHOD, NULL, pDisp, L"call", 3, parm[2], parm[1], parm[0]);
參考
如需使用 Visual C++ 自動化 Office 的詳細資訊,請按一下下列文章編號以檢視 Microsoft 知識庫中的文章:
使用 Visual C++ 196776 Office 自動化
(c) Microsoft Corporation 1999, All Rights Reserved. Microsoft Corporation Joe Crump 的貢獻。