Writing MS WORD Addins

Environment: VC++/ATL COM

Introduction

After having written about developing Office COM addins in an earlier article, I get a lot of mail from people trying to write Word addins. Through this article we will discuss common development issues regarding Word addins in general. First, we learn how to build a simple WORD 2000 ATL COM addin. Later in the article, we will get down to Word's VBA macro side of things, and write an addin that works for all versions of Word, independent of COM addin support.

I'm assuming you have read my previous article on Office addins and taken a look at the sample project. There are a lot of issues in addin development, such as adding custom menu/toolbars, handling events, property pages, and so forth that are common for all Office addins, including Word. These have already been discussed in that article. Here we are going to write a Word 2000 COM addin, to begin with. Later, we'll delve into Office and VBA in general, and with respect to with C++ addins.

Writing a Word 2000 Addin

To begin, create a new ATL COM Appwizard-generated DLL project called WordAddin. Next, insert an ATL Simple Object called Addin, as in the previous article. Next, use the Implement Interface ATL Wizard to implement _IDTExtensibility2. This, as you know, is the at the heart of COM addin support for all Office applications. Next, to program against Word, we need to import Word's typelib. That means, we need to import three interdependent typelibs. In your project's stdafx.h, add the following code:

//for Office XP
//C:\Program Files\\Common Files\\Microsoft Shared\\Office10\\
     MSO.DLL

#import "C:\\Program Files\\Microsoft Office\\Office\\mso9.dll"
         rename_namespace("Office2000")
using namespace Office2000;

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\VBA
           \\VBA6\\VBE6EXT.olb" rename_namespace("VBE6")
using namespace VBE6;

In your project's Addin.h file, towards the top, add:

//for Office XP
//C:\\Program Files\\Microsoft Office\\Office10\\MSWORD.olb

#import "C:\\Program Files\\Microsoft Office\\Office\\MSWORD9.olb"
         rename("ExitWindows","MyExitWindows"),named_guids,
                 rename_namespace("MSWord")
using namespace MSWord;

Make sure that you change the path to point to the correct locations of the files for your system.

The different locations for the #import statements are needed for the compiler to recognize everything, generate the correct set of wrappers, and compile and link correctly. Moving on, to register your addin with Word 2000, add the following code to the Addin.rgs Registry script file (under FileView->Resource Files) and add the following to the end of the file.

HKCU
{
  Software
  {
    Microsoft
    {
      Office
      {
        Word
        {
          Addins
          {
            'WordAddin.Addin'
            {
              val FriendlyName = s 'WORD Custom Addin'
              val Description = s 'Word Custom Addin'
              val LoadBehavior = d '00000003'
              val CommandLineSafe = d '00000001'
            }
          }
        }
      }
    }
  }
}

Yes, we'd like our addin to be called 'Word Custom Addin' ('I love CodeGuru.com Addin' would have been too blatant!), and loaded when Word starts up. So, what else is new?

If everything has gone right, you now have a working Word addin to which you should add your own implementation code. By now, you already know how to add buttons and menu items in your addin, property sheets, and so forth; all that has been discussed in the previous article holds true. The only thing different is the Word Object Model, and from the code in the Outlook addin example, is that there is no ActiveExplorer object in Word. You should get the CommandBars interface directly from Application, the topmost in the object model.

Handling Events

Probably in your addin, you'd also be interested in handling some of Word's events. A case in point is the Application object's DocumentOpen event, with DISPID=4, which is handled here. Word has a complex object model and you will find a host of other such events. If you use the good old OLE/COM Object Viewer to view msword9.olb, you'd find an IDL like this:

    ....

    [id(0x00000003), helpcontext(0x00061a83)]
    void DocumentChange();
    [id(0x00000004), helpcontext(0x00061a84)]
    void DocumentOpen([in] Document* Doc);

    ......

As before, we will use ATL's IDispEventSimpleImpl<> template class to implement our sink. For brevity, only the changes necessary to the earlier code have been mentioned.

extern _ATL_FUNC_INFO DocumentOpenInfo;

class ATL_NO_VTABLE CAddin :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CAddin, &CLSID_Addin>,
  public ISupportErrorInfo,
  public IDispatchImpl<IAddin, &IID_IAddin,
                               &LIBID_WORDADDINLib>,
  public IDispatchImpl<_IDTExtensibility2,
         &IID__IDTExtensibility2,
         &LIBID_AddInDesignerObjects>,
  public IDispEventSimpleImpl<1,CAddin,
         &__uuidof(MSWord::ApplicationEvents2)>
{
public:
....
....

void __stdcall DocumentOpen(IDispatchPtr ptr)
{
  CComQIPtr<_Document> spDoc(ptr);
  ATLASSERT(spDoc);
  ....
  ....

}

BEGIN_SINK_MAP(CAddin)
SINK_ENTRY_INFO(1,__uuidof(MSWord::ApplicationEvents2),4,
                DocumentOpen,&DocumentOpenInfo)
END_SINK_MAP()


private:
CComPtr<MSWord::_Application> m_spApp;
};

DocumentOpenInfo is defined at the top of CAddin.cpp as:

_ATL_FUNC_INFO DocumentOpenInfo = {CC_STDCALL,VT_EMPTY,1,
                                   {VT_DISPATCH|VT_BYREF}};

All that remains for us to do is to add the code to set up and break down the connection. Using the ATL template class, therefore, all we have to do is call DispEventAdvise() and DispEventUnadvise(). Our CAddin's OnConnection() and OnDisconnection(), needless to say, is the right place to do this.

CComQIPtr<_Application> spApp(Application);
ATLASSERT(spApp);
m_spApp = spApp;
HRESULT hr = DispEventAdvise(m_spApp);
if(FAILED(hr))
return hr;

and in OnDisconnection(),

DispEventUnadvise(m_spApp);
m_spApp = NULL;

Now you have a working Word COM addin template, proudly under your belt. To this, you should add your own implementation, error-handling routines, and so on. Moving on to something different, let's explore VBA side of WORD. Next, I'll discuss a very specific scenario where I use a mix of C++ code and VBA macros in my addin.

Macros and the Visual Basic Editor

I was working on a project where the COM addin model of things suited us fine, except that the client had a large number of Word97 users, and Word97 has no support for COM addins, specifically the IDTExtensibility2 interface. Basically, in my addin, I needed to add a few custom menu items and buttons, clicking which usually meant displaying a couple of dialogs. Now, a COM addin would be perfect for Word 2000 users. But was there some way we could make the addin Word97 compatible?

This leads us to Visual Basic for Applications (VBA). Most Office developers know that MS Office components and applications support a rich scripting object model and a scripting interface known as VBA. Before Office version 4, each application in the suite was very distinct. For developers, it wasn't easy to create integrated solutions using multiple Office applications, because each application had a unique programming environment.

This problem was addressed through MS Visual Basic for Applications (VBA), which made its debut in Office 95—albeit in a few applications. By Office 97, every app in the suite supported standard VBA interfaces. The set of functions that a VBA routine, or macro, can use to control its host application is the same set of functions that the OLE Automation client can use to control the application externally, regardless of the programming language for the controller. Word 97 includes Visual Basic 5.0, a sophisticated development environment that is shared across Office applications: Word, Excel, PowerPoint, and Access.

In Word, VBA and Visual Basic go beyond being merely a macro language—it is a full-featured programming development environment. The Visual Basic Editor (VBE) uses the familiar programming interface of Microsoft Visual Basic 4.0 as a base for creating and editing script code. Through VBA, Word supported everything from macros to addins to document templates. So, what is a document template? A document template (*.dot) is simply a document with macro code (script) embedded in it. In Word, macros are persisted in documents and templates as Visual Basic modules. Although macros are ordinarily stored in the user's default template, Normal.dot, Word allows you to store and use macros in any document or template. Moreover, any such document template in the Office installation's Startup folder, would be autorun. Additional templates and macros can be loaded by using the Templates & Add-ins or Macros dialog box in the Tools menu. Macros and document templates (also supported for WordPerfect) can also be shared across users in a Workgroup.

What is also interesting is that MS Word97 onwards also supports a group of global macros that gets invoked with the host application. These AutoXXX macros, such as AutoExec(), AutoNew(), AutoOpen(), AutoClose(), and AutoExit() get executed along with the host application, just as their names suggest. This sounds most useful and it is.

How? If we were to create a document template, handle such Auto macros in it, and then create and destroy our addin (which is written as an ActiveX server) through VBA macros—then we should be on the home stretch. This is what IDTExtensibility2 does for COM addins—primarily a way to connect and disconnect to the topmost Application object. So, how can we substitute something like this in our Word97 addin? Through document templates. Document templates, by themselves, can be described as VBA addins. Every document template is set up to interact with the Document object associated with the project through the ThisDocument property. We must also remember that all macros implicitly reference the Application object.

Back to the task at hand, suppose we were to write a document template and in it, add code to our AutoExec() and AutoExit() handlers. In the handlers, we create and release our addin COM class object and subsequently call its methods. Moreoever, our ATL COM class should implement two methods, Init() and Uninit(), through which the Application object is set in the C++ addin. Our macros ought to look like this:

Dim o as Application
Dim obj as Object

Sub AutoExec()
Set obj = CreateObject("Word97Addin.Addin")
Set o = ThisDocument.Application
obj.Init o
End Sub

Sub AutoExit()
Set o = ThisDocument.Application
obj.Uninit o
Set obj = Nothing
Set o = Nothing
End Sub

We can choose to automatically load and unload our template by placing it in the Office Startup folder. Similarly, you can handle the AutoClose(), AutoNew(), and AutoOpen() macros, which are more document related. For the moment, because I'm handling all button/menu creation and events in my C++ COM object, the communication is unidirectional (i.e. macro->addin), but bidirectional communication is not too difficult. Suppose you have a macro called "NewMacro" defined in your template; one way to trigger this macro from a C++ addin is through Application::Run() or Application::RunOld(), depending on whether you need to pass any parameters. What is interesting is that you can also trigger the macro through a button click, by setting the OnAction property of the CommandBarButton object of your button or menuitem.

// get CommandBarButton interface so we can specify button styles
CComQIPtr < Office::CommandBarButton> spCmdButton(spNewBar);
ATLASSERT(spCmdButton);
spCmdButton->PutCaption(_T("Button")); 
spCmdButton->put_OnAction(OLESTR("NewMacro")); 
//set other button properties
spCmdButton->PutVisible(VARIANT_TRUE);

This is just what I did for my project. We have a single solution, consisting of a DLL and a DOT file, that runs across all versions of Word—from 97 to XP, exposing and encapsulating most of its functionality through compiled C++ code. Unfortunately, this VBA support brings up the security issue, and in later versions of Office, such as XP, the user can determine the security level in the macro execution context. Although this was a non-issue with us, you need to be aware.

All of what we have learned so far has been espoused in the accompanying VC++ 6.0 Universal Addin project, which I will briefly describe next.

Universal Addin

Universal Addin is a simple ATL/COM AppWizard generated DLL project, to which I inserted an ATL COM IDispatch-based Simple Object called Addin. While the name sounds very pretentious, 'Universal' refers to its ability to run across all versions of Word.

What lends it such universality is the addin.dot Word document template that is a part of the addin, and its AutoXXX macros. Our Addin class has two member methods, Init() and Uninit(), that take a single IDispatch* to the Application object. Our Uninit() implementation is very simple and we could have done without the IDispatchPtr parameter; might not be so for your implementation. In the project, we add a single button through the addin, clicking which triggers a VBA named macro, that in turn calls a method of our COM class. By all means, you can connect to CommandBarButtonEvents and write C++ code to handle button click, as done previously.

Out aim was to write an addin for Word, bypassing the the COM addin architecture and IDTExtensibility2. That concluded, all that remains for me to say is that whatever you can do with VBA, you can do from C++ and vice versa—the magic is in OLEAutomation.

And of course, happy coding!

Acknowledgements

Everything I know about Office development, I learned at MSDN. You, too, can find a horde of technical articles and short KB code snippets related to everything from Office development to COM and OLE Automation. MSDN rules—as we all know.

Thanks to Peter Hauptmann (a.k.a. Peterchen) for his comments and suggestions.

Downloads

Download demo project - 21 Kb


Comments

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • In this webinar, IDC featured speaker Steve Conway, Vice President of High Performance Computing, will present an update on the global x86 HPC cluster market. The presentation will include IDC's five-year forecast for the medium- to large-scale technical computing and data analysis emerging markets by systems, processors and application middleware. Cray's featured speaker, John Lee, Vice President of Cray Cluster Advanced Technology Systems, will present the new Cray® CS400™ cluster series based on …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds