Automatisieren von Excel aus C++ ohne MFC oder #import

Zusammenfassung

Es gibt mehrere Vorteile beim Schreiben ihres Automatisierungscodes in geradem C++. In erster Linie können Sie genau das tun, was Sie möchten. Als Nächstes ist Ihr Code kleiner, schneller und einfacher zu debuggen. Und schließlich sind Sie nicht von Bibliotheken abhängig. Auch wenn Sie sich der Verwendung der Wrapperklassen von MFC oder der systemeigenen COM-Unterstützung (#import) von Visual C++ widmen, müssen Sie sich möglicherweise noch mit den Grundlagen von IDispatch und COM-Automatisierung beschäftigen, um häufige Fehler und Einschränkungen mit diesen Frameworks zu umgehen.

Weitere Informationen

Führen Sie die folgenden Schritte aus, um eine einfache Visual C++ 6.0-Konsolenanwendung zu erstellen, die Microsoft Office Excel mit nur C++ automatisiert:

  1. Starten Sie Visual C++ 6.0, und erstellen Sie eine neue Win32-Konsolenanwendung mit dem Namen XlCpp. Wählen Sie eine Anwendungsbasis "Hello, World!" aus, und klicken Sie auf "Fertig stellen".
  2. Öffnen Sie die generierte Datei "XlCpp.cpp", und fügen Sie den folgenden Code vor der main()-Funktion hinzu.
    #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;
    }
    
  3. Ersetzen Sie in der Main()-Funktion die Zeile "printf()" durch den folgenden Code.
    // 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();
    
  4. Kompilieren und ausführen.

Die AutoWrap()-Funktion vereinfacht die meisten Details auf niedriger Ebene, die mit der direkten Verwendung von IDispatch verbunden sind. Sie können es in Ihren eigenen Implementierungen verwenden. Eine Einschränkung ist, dass sie in umgekehrter Reihenfolge übergeben werden müssen, wenn Sie mehrere Parameter übergeben. Beispiel:

    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]);

References

Weitere Informationen zum Automatisieren von Office mit Visual C++ finden Sie in den folgenden Artikelnummern, um die Artikel in der Microsoft Knowledge Base anzuzeigen:

196776 der Office-Automatisierung mit Visual C++

(c) Microsoft Corporation 1999, Alle Rechte vorbehalten. Beiträge von Joe Crump, Microsoft Corporation.