HOWTO: Use Visual C++ to Access DocumentProperties with Automation

ID: Q238393


The information in this article applies to:
  • Microsoft Word 97 for Windows
  • Microsoft Word 2000
  • Microsoft Visual C++, 32-bit Professional Edition, version 6.0


SUMMARY

This article illustrates how you can automate Microsoft Word with Visual C++ to retrieve and 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

Follow the steps below to build a simple Visual C++ 6.0 console application that automates Microsoft Word using just Visual C++.

  1. Start Visual C++ 6.0 and create a new Win32 Console Application named AutoWord. Choose a "Hello, World!" application base and click Finish.


  2. Open the generated AutoWord.cpp and replace its contents with the following code:


  3. 
    #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;
    } 
  4. Use Microsoft Word to create a new document and save it as C:\Doc1.doc. Otherwise, the error 0x800A1436 (-2146823114) appears. This error indicates that "the file does not exist" when the code above attempts to open it.


  5. Compile and run.


The code demonstrates reading and writing both the built-in and custom document properties. When run, it displays the value of the built-in Subject property, changes its value to This is my subject and creates a new custom document property called CurrentYear. When prompted to Examine Document by the code, switch to Microsoft Word and choose Properties from the File menu. When done, read through the comments in the code to learn how it works.

NOTE: The 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. Thus, 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 using Visual C++, please see the following articles in the Microsoft Knowledge Base:

Q196776 FAQ: Office Automation Using Visual C+
Q179494 HOWTO: Use Automation to Retrieve Built-in Document Properties

© Microsoft Corporation 1999, All Rights Reserved.
Contributions by Lori Turner, Microsoft Corporation

Additional query words: documentproperties document properties builtin built-in automating office

Keywords : kbAutomation kbVC kbVC600 kbGrpDSO kbfaq kbDSupport kbWord97 kbword2000
Version : WINDOWS:2000,97; winnt:6.0
Platform : WINDOWS winnt
Issue type : kbhowto


Last Reviewed: September 23, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.