Microsoft Visual C++ .NET 2003 Kick Start
Using Word as a Spell Checker from Managed C++
Just as you can add the power of other applications to your unmanaged applications, so too you can add it to managed apps. But instead of #import , type libraries, and progids , you have to learn your way around references, interop assemblies, and the GAC. Primary Interop Assemblies
The temptation is strong to think you know how to get to Word from managed code; just add a reference on the COM tab, it will make you a Runtime Callable Wrapper (RCW) and away you go. But the last thing you want is to create your own RCW. Word comes with a set of Primary Interop Assemblies, or PIAs. A PIA does what you would expect any RCW to do; it looks like a .NET assembly on the outside, but it doesn't have implementation code. Instead it knows where to find that implementation code in COM, and how to marshal and convert back and forth between the managed and unmanaged worlds . What sets a PIA apart from the kind of RCW you could make yourself by just adding a reference? A PIA
Make no mistake about it, if you're going to automate Word from managed code, you want to use the PIAs. When you install Office 2003 on a machine that already has the .NET Framework installed (and therefore already has a GAC), you can install the PIAs into the GAC. Look for the .NET Programmability Support options. If Office 2003 is already installed, you can change your installation as follows :
Open Windows Explorer and browse to your Windows directory ( C:\Windows or C:\Winnt ) and then to the assembly directory beneath that. This shows you all the assemblies in the GAC. Make sure you see plenty of names that start with Microsoft.Office.Interop . Creating the Sample Application
Create a managed console application called ManagedWord . Bring up the Add Reference dialog box and switch to the COM tab. Scroll until you find Microsoft Office 11.0 Library. Click Select and then OK. The reference is added, along with several dependent references. However, an RCW is not generated for you. In Solution Explorer, expand the References node and select Microsoft.Office.Interop.Word . Switch to the Properties window and you will see the properties of the reference itself. The Full Path property shows where the assembly is located: You will see a path like this:
[View full width]
[View full width] c:\Windows\assembly\Gac\Microsoft.Office.Interop.Word.0.0.0__71e9bce111e9429c\Microsoft![]() This reminds you that the PIA from the GAC was used, rather than a new RCW generated for you by the tlbimp utility. The code for the main() function performs exactly the same tasks as the unmanaged version you've just seen, but because it is accessing Word functionality through a managed interface, there are a few slight differences at these stages:
Rather than the smart pointers generated by #import , in managed code you create an instance of the coclass, and keep it in a pointer to the interface, like this:
Microsoft::Office::Interop::Word::Application* ap = new Microsoft::Office::Interop::Word::ApplicationClass(); To discover the name of the interface and the coclass, double-click the Microsoft.Office.Interop.Word reference in Solution Explorer. This brings up the Object Browser, which serves much the same purpose as the OLE/COM object viewer. You can select an interface from the tree view on the right, and see the details of the functions it holds on the left. When you select an interface, the bottom pane holds details about the interface itself. For example, if you select Application on the left, under the node for the Microsoft.Office.Interop.Word namespace, you will see the details shown in Figure 9.1. Figure 9.1. The Object Browser provides all the information you need about the managed interface.
The CheckSpelling() method is just like the equivalent method in the unmanaged interface, with one important exception: optional parameters. Like many of the methods exposed in the managed interfaces, CheckSpelling() expects parameters to be passed by reference. From C++ that means that you must pass an address of (a pointer to) a managed instance. This syntax is hard to reconcile with optional parameters. The call from unmanaged code was
bool spellingOK = ap->CheckSpelling(word); From managed code, all the parameters must be supplied. You can pass along a special value that means you are not supplying the parameter, but you must pass it by reference, like this:
Object* missing = System::Reflection::Missing::Value; bool spellingOK = ap->CheckSpelling(s,&missing,&missing, &missing,&missing, &missing,&missing, &missing,&missing, &missing,&missing, &missing,&missing); This is fairly tedious , but it works. Because the Office libraries are so rich in parameters passed by reference, C++ and C# programmers have to do a little more work to use them than Visual Basic programmers. But don't let that stop you! In the unmanaged version of the spell checker, the strings were just char* pointers and good old C runtime library functions like strtok were the order of the day. In the managed version, it's a little different, although the concepts are the same. The strings are System::String instances, and the Split() method breaks it up according to delimiters you specify. But instead of calling it repeatedly, as with strtok() , you call it once to create an array. The loop is governed not by the return from strtok() but by an enumerator that goes through the array. You call MoveNext() to point the enumerator at the next element of the array, and use the Current property to access the current element. Finally, the managed version of Quit() is multiply defined, so it's simpler to just close the active window. Because Word exits when the last document is closed, this will ensure you are not left with extra copies of WINWORD.EXE running. To pass the save options constant, you need to create an instance of Object (so you can pass the address to the method, which expects its parameters by reference) and then box the constant into the object, like this:
Object* donotsave = __box( Microsoft::Office::Interop::Word::WdSaveOptions::wdDoNotSaveChanges); ap->get_ActiveWindow()->Close(&donotsave,&missing); The Entire Managed Application
The managed version of the spelling checker is in Listing 9.2. Compare it to the unmanaged version. Listing 9.2 ManagedWord.cpp
// This is the main project file for VC++ application project // generated using an Application Wizard. #include "stdafx.h" #using <mscorlib.dll> using namespace System; int _tmain() { Microsoft::Office::Interop::Word::Application* ap = new Microsoft::Office::Interop::Word::ApplicationClass(); Object* missing = System::Reflection::Missing::Value; //to get suggestions, there must be a document if (ap->Documents->Count == 0) ap->Documents->Add(&missing,&missing,&missing,&missing); Console::WriteLine("Enter a sentence and press enter:"); String* testSentence; testSentence = Console::ReadLine(); String* delims = S" \t,() "; Char delimiter[] = delims->ToCharArray(); String* words[] = 0; words = testSentence->Split(delimiter); System::Collections::IEnumerator* nextword = words->GetEnumerator(); while (nextword->MoveNext()) { String* s = __try_cast<String*>(nextword->Current); bool spellingOK = ap->CheckSpelling(s,&missing,&missing,&missing, &missing,&missing,&missing, &missing,&missing,&missing, &missing,&missing,&missing); if (!spellingOK) { Console::WriteLine("{0} is not recognized by Word. Word suggests:",s); Microsoft::Office::Interop::Word::SpellingSuggestions* sugg = ap->GetSpellingSuggestions(s,&missing,&missing,&missing,&missing, &missing,&missing,&missing,&missing, &missing,&missing,&missing,&missing, &missing); int suggcount = sugg->Count; for (int i = 1; i <= suggcount; i++) { Microsoft::Office::Interop::Word::SpellingSuggestion* suggestedword = sugg->get_Item(i); if (suggestedword) { Console::WriteLine(suggestedword->Name); } } if (suggcount == 0) Console::WriteLine("No suggestions."); } } Object* donotsave = __box( Microsoft::Office::Interop::Word::WdSaveOptions::wdDoNotSaveChanges); ap->get_ActiveWindow()->Close(&donotsave,&missing); return 0;} The concepts involved in accessing Word are clearly parallel in the managed and unmanaged code; the interface names, method names, and meanings of the parameters are the same. The effort you put into learning your way around a COM interface is immediately applicable to the RCW (or better still, PIA) version of that interface. The skill of learning your way around an unmanaged COM interface using the OLE/COM object viewer is parallel to the skill of learning your way around a managed .NET interface using the Object Browser. |