Pieter van der Westhuizen

How to merge several Office add-in projects into one binary

You know, I love working for Add-in Express! I think there are few companies that cover so many interesting things to write about. Take this article for example, Eugene asked me to write an article on how the Add-in Express team merged a number of add-ins into one assembly when they build the AbleBits Excel Add-ins Collection.

When I first heard about this topic, I had no idea how to even tackle this problem. Fortunately, one of the perks of writing this blog for Add-in Express is access to the best MS Office developers in the world, and Aleksey send me a sample project. Phew!

The scenario

Before we have a look at the sample project, let’s first examine the scenario for when you might want to consider using this approach. In the case of the Ultimate Suite for Excel, loading was a crucial factor, by following this approach the team effectively reduced the loading time by nearly 60%, because only one assembly needs to be loaded instead of 20+ separate ones.

Now if you ask me, a 60% speed increase is definitely worth-it! In this example, we’ll have two separate Microsoft Excel add-ins. One will have a custom Ribbon Tab including a Ribbon group with a few buttons and a custom Excel Task pane.

The Ribbon Tab and Excel Task pane components of the first add-in

The other add-in will have a custom Ribbon Tab, custom Task Pane as well as some keyboard shortcuts and an Excel App Events component.

The Ribbon Tab, Task Pane, Keyboard Shortcuts and Excel Events components of the second add-in

Before we can merge our Excel add-ins we need to create another plug-in which we’ll use to combine all the elements of the other add-ins.

Start by adding a new ADX COM Add-in project to the solution. Because the other two add-ins only support Excel, you need to select Excel as the supported application. Once the project has been created create two folders in the project called Addin1 and Addin2

Right-click on the Addin1 folder and select Add > Existing Item… Browse to the Addin1 project folder and select AddinModule.cs and the task pane class, in this example it is called ADXExcelTaskPane1.cs, and click the arrow on the right hand side of the Add button and select Add As Link.

The Ribbon Tab, Task Pane, Keyboard Shortcuts and Excel Events components of the second add-in

Repeat the same process for the second plugin, only this time links the files in the Addin2 folder. Once done, the Visual Studio Solution Explorer should look similar to the following image:

The structure of Visual Studio solution

Merging the add-in user interfaces

Since we’re merging our Excel add-ins we have to combine things like Ribbons tabs into a single Ribbon Tab and command bars into a single command bar. To do this, you need to add an empty ADXRibbonTab and ADXCommandBar control to the MergedAddin project’s AddinModule designer surface.

Once the required components are in place, switch to the AddinModule‘s code view and add a new List<T> object to the class:

private List<ADXAddinModule> _modules = new List<ADXAddinModule>();

We’ll add all the modules we need to merge to this collection and loop through each module’s components and add it to the MergedAddin project’s components.

public AddinModule()
{
  Application.EnableVisualStyles();
  InitializeComponent();
 
  if (Process.GetCurrentProcess().ProcessName.ToLower() == "devenv")
      return;
 
  _modules.Add(new Addin1.AddinModule());
  _modules.Add(new Addin2.AddinModule());
 
  for (int i = 0; i < _modules.Count; i++)
  {
    System.ComponentModel.IContainer moduleComponents = _modules[i].GetContainer();
    for (int j = moduleComponents.Components.Count - 1; j >= 0; j--)
    {
      // Keyboard Shortcuts
      if (moduleComponents.Components[j] is AddinExpress.MSO.ADXKeyboardShortcut)
      {
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Excel Task Panes
      if (moduleComponents.Components[j] is AddinExpress.XL.ADXExcelTaskPanesCollectionItem)
      {
        this.adxExcelTaskPanesManager1.Items.Add(moduleComponents.Components[j] as
			AddinExpress.XL.ADXExcelTaskPanesCollectionItem);
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Ribbon controls
      if (moduleComponents.Components[j] is IADXRibbonComponent)
      {
        if (moduleComponents.Components[j] is ADXRibbonTab)
          continue;
        if (moduleComponents.Components[j] is ADXRibbonGroup)
          this.adxRibbonTab1.Controls.Add(moduleComponents.Components[j] as ADXRibbonGroup);
 
        this.components.Add(moduleComponents.Components[j]);
        continue;
      }
 
      // Excel events
      if (moduleComponents.Components[j] is ADXExcelAppEvents)
      {
        BindEvents(moduleComponents.Components[j], this.adxExcelEvents);
        continue;
      }
 
      // Task Panes
      if (moduleComponents.Components[j] is AddinExpress.XL.ADXExcelTaskPanesManager)
      {
        BindEvents(moduleComponents.Components[j], this.adxExcelTaskPanesManager1);
        continue;
      }
    }
    // Module events
    BindEvents(_modules[i], this);
  }
}

The BindEvents method does exactly as its name suggests and will bind the events of the source component to the destinations component in the MergedAddin project.

private void BindEvents(object source, object destination)
{
  System.Reflection.EventInfo[] events = source.GetType().GetEvents();
  if (events == null) return;
  for (int i = 0; i < events.Length; i++)
  {
    System.Reflection.EventInfo eInfo = events[i];
    System.Reflection.FieldInfo fieldSource = 
		source.GetType().GetField(eInfo.Name,
			System.Reflection.BindingFlags.NonPublic |
			System.Reflection.BindingFlags.Instance);
    if (fieldSource == null) // addin module (!!!)
    {
      fieldSource = source.GetType().BaseType.GetField(eInfo.Name,
          System.Reflection.BindingFlags.NonPublic |
          System.Reflection.BindingFlags.Instance);
    }
    if (fieldSource != null)
    {
      Delegate delegateSource = fieldSource.GetValue(source) as Delegate;
      if (delegateSource != null)
      {
        System.Reflection.FieldInfo fieldDestination = 
			destination.GetType().GetField(eInfo.Name, 
				System.Reflection.BindingFlags.NonPublic | 
				System.Reflection.BindingFlags.Instance);
        if (fieldDestination == null)
        {
          fieldDestination = destination.GetType().BaseType.GetField(eInfo.Name,
              System.Reflection.BindingFlags.NonPublic |
              System.Reflection.BindingFlags.Instance);
        }
        if (fieldDestination != null)
        {
			System.Reflection.EventInfo eInfoDestination = 
				destination.GetType().GetEvent(eInfo.Name);
			if (eInfoDestination != null)
			{
				eInfoDestination.AddEventHandler(destination, delegateSource);
			}
        }
      }
    }
  }
}

If you build, register and run the MergedAddin project, you might see some strange behaviour as some of the components are added twice. To resolve this, you need to open the AddinModules of both Addin1 and Addin2 projects and add the following above the class name:

[ComVisible(false)]

Now, when you run your project you should see the UI elements of the two Excel add-ins combined into one.

Merged add-ins in Excel 2010:

The merged UI elements of both add-ins in Excel 2010

There you have it! With a little bit of the Add-in Express teams’ know-how and some innovation you do not need to rewrite your add-ins to combine them into a single binary.

Thank you for reading. Until next time, keep coding!

Available downloads:

This sample Excel Add-in was developed using Add-in Express for Office and .net:

C# sample project for Excel 2007-2013.

14 Comments

  • Clodagh says:

    Hi Pieter,

    thanks for the article, I am also looking to merge modules but with word and this has proved very interesting.

    I do have a question though, if I wished to also use the parent project for common functionalities how would I go about it ?
    Normally, I would add a refernece to the parent project and a using to access these methods but in this case with the links, VS sees double. I think that the combination of the link and the reference create a ciruclar reference.

    Can you advise for this ? What is the difference between a link and a reference ? Could I implement this fonctionality only using references ? (this I will test)

    Thanks

    c

  • Pieter van der Westhuizen says:

    Hi Clodagh

    To share common functionality between your parent project and for example other projects in your solution, you would ideally have to strip out the common methods etc. and place them in a separate project. This way you can add a reference to it from the parent project and any other project in the solution and avoid any circular references.
    It almost sound to me that you want to separate certain functionality in your project? Maybe try using a COM Add-in Additional Module… Have a look at the “Creating modular Office add-ins using Add-in Express” article, it should shed some light.

    Hope this helps. Good luck!

  • Clodagh says:

    Hi Pieter,

    Thanks for the advice, I completely agree with the stripping for a common library, it seems the best way for the common methods etc.

    And thanks for the link, the modular project is exactly what I am looking for, I will implement that today and see how I go, the key part or me being that I can have my separate modules but also that they share a ribbon/command bar.

    Cheers!

  • MARTIN says:

    Hi all,

    I have an error with InitializeComponent when i merge two addin with task pane.
    It seems like a protection level error.
    I’ve tried to add a namespace for my addin 1 (namespace : addin1) and for my addin 2 (namespace : addin2) with no results.

    Help welcome.
    Thanks.

    Martin

  • Pieter van der Westhuizen says:

    Hi Martin,

    Can you share some more detail about the error you get, please?

  • Clodagh says:

    hi Pietr,

    In the end I tried both of your suggestions and the method in this article proved the better choice for us.

    Although, I am now having the issue that if I click a button in one of my added modules, the CurrentInstance and the hostApplication for this module returns null.

    I don’t quite understand how an onClick() can trigger but the WordApp be null.. and in only one of the two added modules… any suggestions would be greatly appreciated. i’m a bit confused :(

    thx
    c

  • MARTIN says:

    Hi Pieter,

    I wanted to know how to merge 2 addins (addin1 and addin2).

    Both of them contain a single taskpane(and have the same name), an addinmodule with a taskpanemanager.

    I have “namespaced” my two addinmodules and taskpanes

    Exemple :
    ADXPowerPointTaskPane1.vb
    “(…) Namespace MyAddin1
    Public Class ADXPowerPointTaskPane1
    Inherits AddinExpress.PP.ADXPowerPointTaskPane

    Public Sub New()
    MyBase.New()

    InitializeComponent()(…)”

    AddinModule.vb
    “(…) Namespace MyAddin1
    ‘Add-in Express Add-in Module
    _
    _
    Public Class AddinModule
    Inherits AddinExpress.MSO.ADXAddinModule
    Friend WithEvents AdxPowerPointTaskPanesCollectionItem1 As AddinExpress.PP.ADXPowerPointTaskPanesCollectionItem
    Friend WithEvents AdxPowerPointTaskPanesManager1 As AddinExpress.PP.ADXPowerPointTaskPanesManager
    Public Sub New()
    MyBase.New() (…)”

    My error :
    InitializeComponent() in both Addinmodule is unaccessible (of course, it’s declared as private).
    Can you help me with this please ? :)

  • Pieter van der Westhuizen says:

    Hi Clodagh,

    Can you send a sample add-in that causes the same problem to the Add-in Express support email, please?
    It’ll make finding the problem easier.

  • Pieter van der Westhuizen says:

    Hi Martin,

    Can you send the project that gives you the error to our support mail? Will be easier to find the problem that way.

  • Adriana says:

    Hello!

    Let’s say Addin1 has a ribbon button with an onclick event that calls an “ExcelApp.Calculate()”… In this scenario I am getting an exception because HostApplication is null for Addin1.
    The same happens if I have any reference to ExcelApp in Addin2, HostApplication -and therefore ExcelApp- is null.

    However, MergeAddin’s HostApplication/ExcelApp works. But, how do I reference it in any of the “sub-addins”?

    I have used my project and the sample project provided with this article and I have this same issue in both.

  • Pieter van der Westhuizen says:

    Hi Adriana,

    Mmm…not sure on why you’re seeing this problem. Would you mind sending your test add-in to out support e-mail?
    It’ll be easier to track down the problem if I see your code.

    Thank you!

  • Andrei Smolin (Add-in Express Team) says:

    Hello Adriana,

    Thank you for sending the project.

    To fix the issue with ExcelApp returning null (Nothing in VB.NET) , you need to use conditional compiling. Say, you can define a conditional variable called Merged, you can define the CurrentInstance and ExcelApp properties of the original modules as follows:

    #if Merged
    public static new MergedAddin.AddinModule CurrentInstance {
    get {
    return AddinExpress.MSO.ADXAddinModule.CurrentInstance as MergedAddin.AddinModule;
    }
    }
    public Excel._Application ExcelApp {
    get {
    return MergedAddin.AddinModule.CurrentInstance.ExcelApp;
    }
    }
    #else
    public static new AddinModule CurrentInstance {
    get {
    return AddinExpress.MSO.ADXAddinModule.CurrentInstance as AddinModule;
    }
    }
    public Excel._Application ExcelApp {
    get {
    return (HostApplication as Excel._Application);
    }
    }
    #endif

    That is, when the module above (Addin1.AddinModule or Addin1.AddinModule) is used in the merged add-in, the ExcelApp property is taken from the ExcelApp property of MergedAddin.AddinModule. The same applies to CurrentInstance. But. Those buts… There’s a problem with AddinExpress.MSO.ADXAddinModule.CurrentInstance: creating a new add-in module instance makes ADXAddinModule.CurrentInstance return this instance. To avoid this, you add the following code line to the constructor of the merged add-in module:

    ADXAddinModule.CurrentInstance = this;

    In VB.NET:

    ADXAddinModule.CurrentInstance = Me

  • Adriana says:

    What is the reason behind having Addin1 and Addin2 added to MergedAddin as a link?

    Why not added as a reference in MergedAddin to the Addin1 and addin2 projects? Why would this not work?

  • Dmitry Kostochko (Add-in Express Team) says:

    Hi Adriana,

    It’s because the source code of 2 add-ins (Addin1 and Addin2) is required for MergedAddin to be built. Adding only references won’t work, since we take both source codes and combine them in the new add-in.

Post a comment

Have any questions? Ask us right now!