Click here to Skip to main content
15,867,453 members
Articles / Productivity Apps and Services / Microsoft Office
Article

Driving Microsoft Word, using VOLE

Rate me:
Please Sign up or sign in to vote.
4.95/5 (12 votes)
10 Aug 20076 min read 65.9K   874   32   12
An alternative take on describing the VOLE Automation library

Introduction

This article is the second in a series describing the VOLE C++/COM Automation driver library. (The first one is here.) It illustrates the use of VOLE to rewrite (and dramatically simplify) Microsoft's own code example for how to drive Word's automation object model from C++.

Background

A new user of VOLE recently emailed me to ask whether VOLE could handle driving Microsoft Word, to achieve the functionality involved in Microsoft's own code example. He wrote:

"I have just come across your library while looking for a reasonably sane way to add a bit of Excel automation in my C++ program. Not being familiar with COM I found this example http://support.microsoft.com/kb/238393 too scary. Then I found your wrapper that promises to make COM easier to use."

He went on to say:

"The fact that I still have to deal with code like this

C++
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);

is not comforting. I haven't played that much with the code I downloaded from your site but is it possible that you library can actually hide this stuff behind a modern c++ interface?"

The simple answer is: Yes.

In this article I will rewrite the Microsoft example with VOLE, illustrating how to drive COM Automation servers that have sophisticated object models.

The Scenario

The Microsoft example does the following actions:

  1. Look up the CLSID for "Word.Application"
  2. Start Word and get an IDispatch pointer for the application object
  3. Make Word visible
  4. Get the Documents collection
  5. Call Documents.Open() to open "C:\Doc1.doc"
  6. Get the BuiltinDocumentProperties collection
  7. Get the "Subject" property from the BuiltInDocumentProperties collection
  8. Get the Value of the "Subject" property
  9. Set the Value of the "Subject" DocumentProperty
  10. Get CustomDocumentProperties collection
  11. Add a new property named "CurrentYear"
  12. Get the custom property "CurrentYear" and delete it
  13. Close the document without saving changes
  14. Quit Word

The VOLE Version

You can see the full horror of the manual method of achieving this in C++ in the Microsoft example. Now let's look at how it's achieved using VOLE.

C++
// Step 2
object word = object::create("Word.Application", CLSCTX_LOCAL_SERVER);

// Step 3
word.put_property(L"Visible", true);

// Step 4
collection documents = word.get_property<collection>(L"Documents");

// Step 5
object document = documents.invoke_method<object>(L"Open", L"C:\\Doc1.doc");

// Step 6
collection builtInProps = document.get_property<collection>(
    L"BuiltinDocumentProperties");

// Step 7
object propSubject = builtInProps.get_property<object>(L"Item", L"Subject");

// Step 8
std::string subject = propSubject.get_property<std::string>(L"Value");

std::cout << "Subject property: \"" << subject << '"' << std::endl;

// Step 9
propSubject.put_property(L"Value", "This is my subject");

// Step 10
collection customProps = document.get_property<collection>(
    L"CustomDocumentProperties");

// Step 11
customProps.invoke_method<void>(L"Add", L"CurrentYear", false, 1, 1999);

// Step 12
object propCurrYear = customProps.get_property<object>(
    L"Item", L"CurrentYear");

propCurrYear.invoke_method<void>(L"Delete");

// Step 13
document.invoke_method<void>(L"Close", false);

// Step 14
word.invoke_method<void>(L"Quit");

The full program is included in the download; here I'll just show the salient parts. For clarity here, we'll assume that using declarations have been employed for the VOLE classes.

Step 1. Look up the CLSID for "Word.Application"

This step is easy: we can omit it entirely.

Because the (static) vole::create() methods allow the user to specify the class to be create via CLSID ({000209FF-0000-0000-C000-000000000046}), Programmatic Id ("Word.Application"), or string form of the CLSID ("{000209FF-0000-0000-C000-000000000046}"), we can just pass the Programmatic Id.

Step 2. Start Word and Get an IDispatch Pointer for the Application Object

To start the Word automation server, we pass "Word.Application" to the vole::object::create() method. This method returns an instance of vole::object that owns the underlying COM interface associated with the server.

C++
object word = object::create("Word.Application", CLSCTX_LOCAL_SERVER);

Note that, just as in the Microsoft example, we must specify CLSCTX_LOCAL_SERVER to invoke Word as a local server.

Step 3. Make Word Visible

To make the Word application object visible, we set its Visible property to true. This is done by using the vole::object::put_property() method, as in:

C++
word.put_property(L"Visible", true);

Step 4. Get the Documents collection

For this we need to elicit the application object's Documents property, in an instance of vole::collection, via the vole::object::get_property() method, as follows:

C++
collection documents = word.get_property<collection>(L"Documents");

Note the explicit specialisation of the member function template vole::object::get_property(). All modern compilers are able to handle this modest sophistication, but some (in particular VC++ 6) are not. I'll show the alternate code, where required, for each step at the end of this article.

Step 5. Call Documents.Open() to Open "C:\Doc1.doc"

To invoke an Automation server's methods, we use the vole::object::invoke_method():

C++
object document = documents.invoke_method<object>(L"Open", L"C:\\Doc1.doc");

This returns an instance of vole::object that holds the corresponding document object.

Note: just as with the Microsoft example, you need to make sure that the file C:\Doc1.doc exists, otherwise an exception will be thrown.

Step 6. Get the BuiltinDocumentProperties collection

We elicit the document's BuiltinDocumentProperties property into a collection in the same way as shown in Step 4.

C++
collection builtInProps = document.get_property<collection>(
   L"BuiltinDocumentProperties");

Step 7. Get the "Subject" Property from the BuiltInDocumentProperties Collection

And here also:

C++
object propSubject = builtInProps.get_property<object>(L"Item", L"Subject");

Step 8. Get the Value of the "Subject" Property

This time we elicit a property value that is a string, rather than an object. To that end, we request is as an instance of std::string.

C++
std::string subject = propSubject.get_property<std::string>(L"Value");

Note that VOLE equally well supports elicitation of string values as instances of std::wstring, so we could have written it as:

C++
std::wstring subject = propSubject.get_property<std::wstring>(L"Value");

Step 9. Set the Value of the "Subject" DocumentProperty

This is achieved via the vole::object::put_property() method:

C++
propSubject.put_property(L"Value", "This is my subject");

Note that VOLE handles conversion between ANSI/multibyte and wide/Unicode strings automatically for method/property parameters. Hence, we could just as readily have written:

C++
propSubject.put_property(L"Value", L"This is my subject");

The only difference between them is that the former is slightly less efficient, because the string "This is my subject" has to be converted to wide form.

Note: This flexibility does not apply to the names of the methods/properties themselves - these must be specified either as a DISPID (aka a long) or as a wide-string.

Step 10. Get CustomDocumentProperties collection

This is essentially the same as Step 6.

C++
collection customProps = document.get_property<collection>(
    L"CustomDocumentProperties");

Step 11. Add a New Property Named "CurrentYear"

We invoke the new property's Add() method using vole::object::invoke_method, as follows:

C++
customProps.invoke_method<void>(L"Add", L"CurrentYear", false, 1, 1999);

Because we do not return anything, we specialise as void.

Step 12. Get the Custom Property "CurrentYear" and Delete It

To delete the property we've just created, we need to first retrieve it and then instruct the retrieved property object to delete itself, as follows:

C++
object propCurrYear = customProps.get_property<object>(
    L"Item", L"CurrentYear");
propCurrYear.invoke_method<void>(L"Delete");

Alternatively, we could have achieved this in one statement, as follows:

C++
customProps.get_property<object>(
    L"Item", L"CurrentYear").invoke_method<void>(L"Delete");

But that's less clear. Obviously, VOLE, although very expressive compared to raw C++ manipulation of COM Automation servers, still has a degree of verbosity to it. I tend, therefore, to try and keep each COM property/method invocation to a single C++ statement.

Step 13. Close the Document Without Saving Changes

Since we've modified the document object, trying to quit Word will fail because it will ask us whether we want to save our changes. So we first call the document method Close, passing false to indicate that we want to discard the changes.

C++
document.invoke_method<void>(L"Close", false);

Step 14. Quit Word

Finally, we instruct Word to quit, which causes the Word application to shut down.

word.invoke_method<void>(L"Quit");

And that's all there is to it. Compared to the ~220 lines of code in the Microsoft example it's a huge win in expressiveness. Add in the flexibility in handling different fundamental types and character encodings, the robustness in that all server resources are handled in an exception-safe manner, and the fact that it's 100% header-only and compiler-independent, it's pretty clear that VOLE represents an optimal solution for driving COM Automation servers from C++. But I would say that, wouldn't I? So I invite you to try it out for yourselves.

Compatibility with Older Compilers

As described in the first article in this series, some old compilers (e.g. VC++ 6) have problems with VOLE's template syntax, so an alternate form is also supported. The alternate form is pertinent to the following steps:

C++
// Step 4
collection documents = word.get_property(of_type<collection>(), L"Documents");

// Step 5
object document = documents.invoke_method(
    of_type<object>(), L"Open", L"C:\\Doc1.doc");

// Step 6
collection builtInProps = document.get_property(
    of_type<collection>(), L"BuiltinDocumentProperties");

// Step 7
object propSubject = builtInProps.get_property(of_type<object>(), 
    L"Item", L"Subject");

// Step 8
std::string subject = propSubject.get_property(of_type<std::string>(), 
    L"Value");

// Step 10
collection customProps = document.get_property(of_type<collection>(), 
    L"CustomDocumentProperties");

// Step 11
customProps.invoke_method_v(L"Add", L"CurrentYear", false, 1, 1999);

// Step 12
object propCurrYear = customProps.get_property(of_type<object>(), 
    L"Item", L"CurrentYear");
propCurrYear.invoke_method_v(L"Delete");

// Step 13
document.invoke_method_v(L"Close", false);

// Step 14
word.invoke_method_v(L"Quit");

More to Come ...

I plan to write another article about VOLE soon, illustrating the vole::collection class's ability to provide STL iterators over a COM Collection's elements (via the IEnumXXXX protocol).

Your comments/criticisms/feature requests for VOLE are welcome via the VOLE project home.

Your comments/criticisms/feature requests for STLSoft are welcome via the STLSoft newsgroup (here), which is kindly provided by Digital Mars, providers of free high-quality C/C++/D compilers.

History

5th August 2007: First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Instructor / Trainer
Australia Australia
Software Development consultant, specialising in project remediation.

Creator of the FastFormat, Pantheios, STLSoft and VOLE open-source libraries.

Author of the books Extended STL, volume 1 (Addison-Wesley, 2007) and Imperfect C++ (Addison-Wesley, 2004).

Comments and Discussions

 
QuestionDoes not compile... Pin
zlazic13-Dec-11 6:53
zlazic13-Dec-11 6:53 
AnswerRe: Does not compile... Pin
bcheung@rocketmail.com13-Mar-14 17:02
bcheung@rocketmail.com13-Mar-14 17:02 
Questionwhy wait about 4 seconds when quit word? Pin
jackyxinli28-Sep-10 19:59
jackyxinli28-Sep-10 19:59 
QuestionHow do I create a word table? Pin
Ari Unikoski11-May-09 19:38
Ari Unikoski11-May-09 19:38 
AnswerRe: How do I create a word table? Pin
Ari Unikoski14-May-09 23:31
Ari Unikoski14-May-09 23:31 
I found a solution, which I'm posting in case anyone ever stumbles over this some time in the future....

vole::collection xlTables = xlDoc.get_property<vole::collection>(L"Tables");
comstl::variant xlRange = xlDoc.invoke_method<comstl::variant>(L"Range",0,0);
vole::object xlTable = xlTables.invoke_method<vole::object>(L"Add",xlRange,1,1,1,1);
Questionhow to export/extract image in word doc? Pin
jauming12-Apr-09 2:56
jauming12-Apr-09 2:56 
GeneralVole objects Pin
NiknSt30-Oct-07 5:52
NiknSt30-Oct-07 5:52 
GeneralRe: Vole objects Pin
Matt (D) Wilson30-Oct-07 9:21
Matt (D) Wilson30-Oct-07 9:21 
GeneralRe: Vole objects Pin
NiknSt30-Oct-07 13:29
NiknSt30-Oct-07 13:29 
GeneralRe: Vole objects Pin
Matt (D) Wilson31-Oct-07 9:19
Matt (D) Wilson31-Oct-07 9:19 
GeneralRe: Vole objects Pin
wtwhite31-Mar-09 6:03
wtwhite31-Mar-09 6:03 
GeneralRe: Vole objects Pin
Matt (D) Wilson1-Nov-07 10:10
Matt (D) Wilson1-Nov-07 10:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.