Development tips to create Office add-ins
in Visual Studio (C#, VB.NET)

Add-in Express™
for Microsoft® Office and .net

Add-in Express Home > Add-in Express for Office and .NET > Online Guide > Tips and notes > Development tips

Office add-in development: tips and tricks

You might have an impression that building add-ins is a very simple task. Please don't get too enthusiastic. Sure, Add-in Express for Office and .net makes embedding your code to Microsoft Office applications very simple, but you are to write the application code yourself, and we guess it would be something more intricate than a single call of MessageBox. On this page you will find some tips and tricks that hopefully will be helpful.

Supporting several Office versions in the same project

There are two aspects of this theme:

  • Supporting the CommandBar and Ribbon UI in one project
  • You can add both Command Bar UI and Ribbon UI components onto the add-in module. When the add-in loads, command bar or ribbon controls will show up depending on the host application version. Find additional information in Command bars in the Ribbon UI.
  • Accessing version-specific features of an Office application

Choosing interop assemblies

An Office interop assembly provides the compiler with early-binding information on COM interfaces contained in a given Office application (COM library) of a given version. That's why there are interops for Office 2003, 2007, 2010.

Because Office applications are almost 100% backward compatible, you can still use any interop version to access any version of the host application. There are two things worth mentioning:

When using an interop for an arbitrary Office version, you are required to check the version of the Office application that loads your add-in before accessing two kinds of things: a) that introduced in a newer Office version and b) that missing in an older Office version. For instance, consider developing an Outlook add-in using the Outlook 2003 interop; the add-in must support Outlook 2019 - 2000. Let's examine accessing two properties of the MailItem class: a) MailItem.Sender introduced in Outlook 2010 and b) MailItem.BodyFormat introduced in Outlook 2002.

Since our add-in uses the Outlook 2003 interop, you cannot just write sender = theMailItem.Sender in your code: doing this will cause a compile-time error. To bypass this, you must write a code that checks if the add-in is loaded in Outlook 2010 and use late binding to access that property. "Late binding" means that you use Type.InvokeMember(); look at this article on MSDN or search for samples on our .NET forum.

Since MailItem.BodyFormat is missing in Outlook 2000, you cannot just write bodyFormat = theMailItem.BodyFormat: doing this will fire a run-time exception when your add-in is loaded in Outlook 2000. To bypass this, you must write a code that checks if the add-in is loaded in Outlook 2000 and avoid accessing that property in this case.

The following questions are discussed in the Supporting several Office versions in an add-in. Interop assemblies and late binding article on our technical blog:

  • What interop assembly to choose for your add-in project?
  • How does an interop assembly version influence the development time?
  • How to support a given Office version correctly?

Use the latest version of the loader

Since the code of the loader frequently changes, you must use its latest version. Whenever you install a new Add-in Express version, you need to unregister your add-in, copy adxloader.dll and adxloader64.dll located in {Add-in Express }\Redistributables to the Loader folder of you project; for XLL add-ins, you must also rename it to adxloader.{XLL add-in project name}.dll. After replacing the loader must rebuild (not just build) your project and register it. If everything was done correctly, you'll see the new loader version in adxloader.log (see Loader's log). Find some background info in Insight of Add-in Express Loader.

Several Office versions on the machine

Although Microsoft allows installing multiple Office versions on a PC, it isn't recommended to do so. Below is a rather long citation from an article by Andrew Whitechapel.

First, the Office client apps are COM-based. Normal COM activation relies on the registry. COM registration is a "last one wins" model. That is, you can have multiple versions of a COM server, object, interface or type library on a machine at the same time. Also, all of these entities can be registered. However, multiple versions can (and usually do) use the same identifiers, so whichever version was registered last overwrites any previous one. Also, when it comes time to activate the object, only the last one registered will be activated. COM identity at run time depends on an object's implementation of QueryInterface, but COM identity at the point of discovery depends on GUIDs. GUIDs are used because they provide a guaranteed (for all practical purposes) unique identifier (surprise).

As soon as you put multiple versions of a COM server/object/interface/typelib onto the same machine, you introduce scope for variability. That is, although COM activation will ensure that the GUID-identified object gets used at the point of activation, you've set up the environment such that the object that this GUID identifies can change unpredictably over time - even short periods of time. This is one of the many reasons why it is very difficult to successfully develop solutions on a machine with multiple versions of Office - and one of the reasons we do not support this. But wait, how can this be? Surely a COM interface never versions? That's true, but, first, Office interfaces are not pure COM interfaces - they're automation interfaces, which are allowed to version (while retaining the same GUID). Second, the objects that implement the interfaces are obviously allowed to version, as are the typelibs that describe them.

Please read the rest of the article: Why is VS development not supported with multiple versions of Office?

How to find files on the target machine programmatically?

You can find the actual location of your files on the target PC using the following code:


string location = Assembly.GetExecutingAssembly().CodeBase;
string fullPath = new Uri(location).LocalPath; // path including the dll
string directoryPath = Path.GetDirectoryName(fullPath); // directory path

Configuring an add-in

The loader manifest (adxloader.dll.manifest) allows specifying the name of the .config file in the configFileName attribute. By default, it is set to "App.Config". Note however that adding an App.config item to your add-in project is not sufficient. Build your project, open the project’s output folder and find the file name of the configuration file generated by the App.config item; the file name should resemble MyAddin1.dll.config. Now, you need to do two things:

  • specify this file name in the configFileName attribute;
  • make sure your add-in installers deliver this file to the target machine; with Visual Studio Installer, you need to add the .config file (e.g. MyAddin1.dll.config) to the Application Folder, see the File System editor.

See also Add-in Loading Process and Add-in Express Loader Manifest.

Using threads

All object models provided by Office are not thread-safe. Using an object model from a thread other than the main one may produce unpredictable consequences. Once, we read Inspector.Count in a thread; after we stopped doing this, the users stopped complaining of a strange behavior of the Down arrow key when composing an email.

When you need to use an object model in a thread, you can bypass this by using the SendMessage method and OnSendMessage event of the add-in module. One side of those members is described in Wait a little. The other side is that the OnSendMessage event occurs in the main thread. That is, you can send a message from a thread and handle the message in the main thread. See also On using threads in managed Office extensions and HowTo: Work with threads in Microsoft Office COM add-ins on our blog.

Releasing COM objects

Office was designed and built on COM, not .NET. This means that an add-in creates COM objects when dealing with the object model of its host application. All such objects require to be released in the COM way. This is necessary because otherwise the add-in may show a different behavior in a different environment or when the add-in's host application is run programmatically.

How to prevent leaving COM objects unreleased by your code?

  • Don't chain COM calls like in OutlookApp.Explorers.Count, ExcelApp.Workbooks[1] (C#) or PowerPointApp.Presentations.Item(1) (VB.NET).

How to properly release Excel COM objects puts this rule as "1 dot good, 2 dots bad" and explains how to rewrite such calls. In Why doesn't Excel quit you will find what objects are created behind the scene and why they are not released when you chain COM calls.

  • Don't use foreach loops on COM collections.

Such a loop accesses the collection's enumerator internally. The enumerator is a COM object and this is the root of the problem. Use for loops instead.

  • Never release COM objects obtained through the parameters of events provided by Add-in Express.

To create a friendly environment for your add-in, Add-in Express creates COM objects and relies on their state. This is why you must not release these COM objects.

You can find a comprehensive review of typical problems (and solutions) related to releasing COM objects in Office add-ins in this article - When to release COM objects in Office add-ins?

Wait a little

Some things are not possible to do right at the moment; say, you cannot close the inspector of an Outlook item in the Send event of that item. A widespread approach is to use a timer. Add-in Express provides a way to do this by using the <SendMessage> method and <OnSendMessage> event; when you call <SendMessage>, it posts the Windows message that you specified in the methods' parameters and the execution continues. When Windows delivers this message to an internal Add-in Express window, the <OnSendMessage> event is raised. Make sure that you filter incoming messages; there will be quite a lot of them. The <OnSendMessage> event always occurs on the main thread.

A message is an integer greater than 1024. To prevent interfering with messages sent from other sources, you can register a message using the RegisterWindowMessage WinAPI function, please see the function description on MSDN and use a function declaration published on PInvoke.net.

VB.NET code sample:


Private Const WM_USER As Integer = &H400
Private Const MYMESSAGE As Integer = WM_USER + 1000

Private Sub AdxKeyboardShortcut1_Action(sender As Object) _
    Handles AdxKeyboardShortcut1.Action
    Me.SendMessage(MYMESSAGE)
End Sub

Private Sub AddinModule_OnSendMessage _
    (sender As Object, e As AddinExpress.MSO.ADXSendMessageEventArgs) _
    Handles MyBase.OnSendMessage

    If e.Message = MYMESSAGE Then
        ' do your stuff here
    End If
End Sub

C# code sample:


private const int WM_USER = 0x0400;
private const int MYMESSAGE = WM_USER + 1000;
private void adxKeyboardShortcut1_Action(object sender) {
    this.SendMessage(MYMESSAGE);
}
        
private void AddinModule_OnSendMessage(object sender, 
    AddinExpress.MSO.ADXSendMessageEventArgs e) {
    if (e.Message == MYMESSAGE) {
        // do your stuff here 
    }
}

Other code samples are provided in these blogs:

<SendMessage>

  • ADXAddinModule.SendMessage
  • ADXOlForm.ADXPostMessage
  • ADXExcelTaskPane.ADXPostMessage
  • ADXWordTaskPane.ADXPostMessage
  • ADXPowerPointTaskPane.ADXPostMessage

<OnSendMessage>

  • ADXAddinModule.OnSendMessage
  • ADXOlForm.ADXPostMessageReceived
  • ADXExcelTaskPane.ADXPostMessageReceived
  • ADXWordTaskPane.ADXPostMessageReceived
  • ADXPowerPointTaskPane.ADXPostMessageReceived

Developing multiple Office extensions in the same project

Add-in Express supports adding several modules in a project, every module representing an Office extension. That means you can create an assembly containing a combination of several Office extensions. Having several modules in an assembly is a common approach to developing Excel extensions; say you can implement a COM add-in providing some settings for your Excel UDF.

What is essential is that all Office extensions will be loaded into the same AppDomain. The only exception is Excel Automation add-ins - they are loaded into the Default AppDomain (but see What Excel UDF type to choose?).

If several Office extensions are gathered in one assembly, Office loads the assembly once but initializes the extensions in the assembly one at a time. That is, if you have two COM add-ins in the same assembly, one of them may be still not initialized when the first one is ready to work. See also HowTo: Create a COM add-in, XLL UDF and RTD server in one assembly.

See also Deploying Office extensions and Accessing public members of your COM add-in from another add-in or application.

Useful resources

If you didn't find the answer to your questions on this page, please see the HOWTOs section: