Microsoft KB Archive/238393

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

Article ID: 238393

Article Last Modified on 7/13/2004

-

APPLIES TO


 * Microsoft Visual C++ 6.0 Professional Edition
 * Microsoft Visual C++ 6.0 Service Pack 5
 * Microsoft Office Excel 2003
 * Microsoft Excel 2002 Standard Edition
 * Microsoft Excel 2000 Standard Edition
 * Microsoft Excel 97 Standard Edition
 * Microsoft Office PowerPoint 2003
 * Microsoft PowerPoint 2002 Standard Edition
 * Microsoft PowerPoint 2000 Standard Edition
 * Microsoft PowerPoint 97 Standard Edition
 * Microsoft Office Word 2003
 * Microsoft Word 2002 Standard Edition
 * Microsoft Word 2000 Standard Edition
 * Microsoft Word 97 Standard Edition

-



This article was previously published under Q238393



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:  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.  Open the generated AutoWord.cpp file, and then replace its contents with the following code:
 * 1) include "stdafx.h"
 * 2) include 

// // 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; iInvoke(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; }                    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. 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.

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

