How To Use Visual C++ to Access DocumentProperties with Automation

Summary

This article illustrates how to automate Microsoft Word with Microsoft Visual C++ to retrieve and to manipulate document properties. Although the sample in this article is specifically written to automate Word, the same concepts can be applied to Microsoft Excel and Microsoft PowerPoint as well.

More Information

To build a simple Visual C++ 6.0 console application that automates Microsoft Word by using Visual C++, follow these steps:

  1. Start Visual C++ 6.0, and create a new Win32 Console Application that is named AutoWord. Choose a "Hello, World!" application base, and then click Finish.
  2. Open the generated AutoWord.cpp file, and then replace its contents with the following code:
    #include "stdafx.h"
    #include <ole2.h>

    //
    // 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/err0x%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;

    }

    int main(int argc, char* argv[])
    {
    // Initialize COM for this thread...
    CoInitialize(NULL);

    // Get CLSID for Word.Application...
    CLSID clsid;
    HRESULT hr = CLSIDFromProgID(L"Word.Application", &clsid);
    if(FAILED(hr)) {
    ::MessageBox(NULL, "CLSIDFromProgID() failed", "Error",
    0x10010);
    return -1;
    }

    // Start Word and get IDispatch...
    IDispatch *pWordApp;
    hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER,
    IID_IDispatch, (void **)&pWordApp);
    if(FAILED(hr)) {
    ::MessageBox(NULL, "Word not registered properly",
    "Error", 0x10010);
    return -2;
    }

    // Make Word visible
    {
    VARIANT x;
    x.vt = VT_I4;
    x.lVal = 1;
    AutoWrap(DISPATCH_PROPERTYPUT, NULL, pWordApp, L"Visible", 1,
    x);
    }

    // Get Documents collection
    IDispatch *pDocs;
    {
    VARIANT result;
    VariantInit(&result);
    AutoWrap(DISPATCH_PROPERTYGET, &result, pWordApp, L"Documents",
    0);

    pDocs = result.pdispVal;
    }

    // Call Documents.Open() to open C:\Doc1.doc
    IDispatch *pDoc;
    {
    VARIANT result;
    VariantInit(&result);
    VARIANT x;
    x.vt = VT_BSTR;
    x.bstrVal = ::SysAllocString(L"C:\\Doc1.doc");

    AutoWrap(DISPATCH_METHOD, &result, pDocs, L"Open", 1, x);
    pDoc = result.pdispVal;
    SysFreeString(x.bstrVal);
    }

    // Get BuiltinDocumentProperties collection
    IDispatch *pProps;
    {
    VARIANT result;
    VariantInit(&result);
    AutoWrap(DISPATCH_PROPERTYGET, &result, pDoc,
    L"BuiltinDocumentProperties", 0);
    pProps = result.pdispVal;
    }

    // Get "Subject" from BuiltInDocumentProperties.Item("Subject")
    IDispatch *pPropSubject;
    {
    VARIANT result;
    VariantInit(&result);
    VARIANT x;
    x.vt = VT_BSTR;
    x.bstrVal = ::SysAllocString(L"Subject");
    AutoWrap(DISPATCH_PROPERTYGET, &result, pProps, L"Item", 1, x);
    pPropSubject = result.pdispVal;
    SysFreeString(x.bstrVal);
    }

    // Get the Value of the Subject property and display it
    {
    VARIANT result;
    VariantInit(&result);
    AutoWrap(DISPATCH_PROPERTYGET, &result, pPropSubject, L"Value",
    0);
    char buf[512];
    wcstombs(buf, result.bstrVal, 512);
    ::MessageBox(NULL, buf, "Subject", 0x10000);

    }

    // Set the Value of the Subject DocumentProperty
    {
    VARIANT x;
    x.vt = VT_BSTR;
    x.bstrVal = ::SysAllocString(L"This is my subject");
    AutoWrap(DISPATCH_PROPERTYPUT, NULL, pPropSubject, L"Value", 1,
    x);
    ::MessageBox(NULL,
    "Subject property changed, examine document.",
    "Subject", 0x10000);
    SysFreeString(x.bstrVal);
    }

    // Get CustomDocumentProperties collection
    IDispatch *pCustomProps;
    {
    VARIANT result;
    VariantInit(&result);
    AutoWrap(DISPATCH_PROPERTYGET, &result, pDoc,
    L"CustomDocumentProperties", 0);
    pCustomProps = result.pdispVal;
    }

    // Add a new property named "CurrentYear"
    {
    VARIANT parm1, parm2, parm3, parm4;
    parm1.vt = VT_BSTR;
    parm1.bstrVal = SysAllocString(L"CurrentYear");
    parm2.vt = VT_BOOL;
    parm2.boolVal = false;
    parm3.vt = VT_I4;
    parm3.lVal = 1; //msoPropertyTypeNumber = 1
    parm4.vt = VT_I4;
    parm4.lVal = 1999;

    AutoWrap(DISPATCH_METHOD, NULL, pCustomProps, L"Add", 4, parm4,
    parm3, parm2, parm1);
    ::MessageBox(NULL, "Custom property added, examine document.",
    "Custom Property", 0x10000);
    SysFreeString(parm1.bstrVal);
    }

    // Get the custom property "CurrentYear" and delete it
    IDispatch *pCustomProp;
    {
    VARIANT result;
    VariantInit(&result);
    VARIANT x;
    x.vt = VT_BSTR;
    x.bstrVal = ::SysAllocString(L"CurrentYear");
    AutoWrap(DISPATCH_PROPERTYGET, &result, pCustomProps, L"Item",
    1, x);
    pCustomProp = result.pdispVal;
    SysFreeString(x.bstrVal);
    AutoWrap(DISPATCH_METHOD, NULL, pCustomProp, L"Delete", 0);
    ::MessageBox(NULL,
    "Custom property removed, examine document.",
    "Custom Property", 0x10000);
    }

    // Close the document without saving changes and quit Word
    {
    VARIANT x;
    x.vt = VT_BOOL;
    x.boolVal = false;
    AutoWrap(DISPATCH_METHOD, NULL, pDoc, L"Close", 1, x);
    AutoWrap(DISPATCH_METHOD, NULL, pWordApp, L"Quit", 0);
    }

    // Cleanup
    pCustomProp->Release();
    pCustomProps->Release();
    pPropSubject->Release();
    pProps->Release();
    pDoc->Release();
    pDocs->Release();
    pWordApp->Release();


    // Uninitialize COM for this thread...
    CoUninitialize();

    return 0;
    }
  3. Use Microsoft Word to create a new document, and save the new document as C:\Doc1.doc. Otherwise, the error 0x800A1436 (-2146823114) appears. This error indicates that "the file does not exist" when the code tries to open it.
  4. Compile and run.
The code demonstrates reading and writing both the built-in document properties and the custom document properties. When run, the code displays the value of the built-in Subject property, changes its value to This is my subject, and creates a new custom document property that is named CurrentYear. When you are prompted to Examine Document by the code, switch to Microsoft Word, and then click Properties on the File menu. When done, read through the comments in the code to learn how it works.

NoteThe purpose of the AutoWrap function in this sample is to wrap the calls for GetIDsOfNames and Invoke to facilitate automation with straight C++. When you create a DISPPARAMS structure for a call to Invoke, the elements are actually passed in reverse order from what the invoked function expects. Therefore, when you use call AutoWrap to invoke a function with more than one argument, you must pass them in reverse order as illustrated in the sample with the call to invoke DocumentProperties::Add:

      AutoWrap(DISPATCH_METHOD, NULL, pCustomProps, L"Add", 4, parm4,
parm3, parm2, parm1);

References

For additional information about automating Office by using Visual C++, see the following articles in the Microsoft Knowledge Base:

196776 FAQ: Office Automation Using Visual C+
179494 How To Use Automation to Retrieve Built-in Document Properties
Svojstva

ID članka: 238393 - posljednja izmjena: 23. ožu 2009. - verzija: 1

Povratne informacije