Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Writing an MS Word addin

0.00/5 (No votes)
13 Apr 2003 6  
Writing a Word Addin using COM and VB Macros

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 like adding custom menu/toolbars, handling events, property pages etc 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 Word2000 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 3 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\\Micorosft 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 is 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 Word2000, add the following code to 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 CodeProject.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 etc; all that has been discussed in the previous article holds true. The only thing different in the Word Object Model, and from the code in the Outlook addin example, is that there is no ActiveExplorer object in Word, and 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 objects 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 IDL like :

         ....

        [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 has been mentioned.

extern _ATL_FUNC_INFO DocumentOpenInfo;

class ATL_NO_VTABLE CAddin : 
    public CComObjectRootEx,
    public CComCoClass,
    public ISupportErrorInfo,
    public IDispatchImpl,
    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 setup 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 for doing 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 etc. 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 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 Word2000 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, 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 it's debut in Office 95 - albeit, in a few applications. By Office97, 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 goes 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 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 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, MS Word97 onwards also supports a group of global macros that gets invoked with the host application. These AutoXXX macros like AutoExec(), AutoNew(),AutoOpen(),AutoClose() and AutoExit() gets 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 ourAutoExec() and AutoExit() handlers. In the handlers, we create and release our addin COM class object and subsequently call it's methods. Moreover, 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:

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's Startup folder.Similarly you can handle AutoClose(),AutoNew() and AutoOpen() macros, which are more document related. For the moment, since 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 defined in your template called "NewMacro"; 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 it's functionilty through compiled C++ code. Unfortunately, this VBA support brings up the security issue, and in later versions of Office like XP, the user can determine the security level in macro execution context. Although this was a non-issue with us, you need to be aware.

All of what we have learnt so far. has been espoused in the accompanied 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 it's 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 it's 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.

Acknowledgements

Everything I know about Office development, I learnt at MSDN. You, too, can find a hoard 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.

History

  • First revision - 14th April, 2003.

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