People have been building native shared add-ins for Office (and related apps) since their introduction in Office 2000. People have been building managed shared add-ins since the introduction of .NET in 2002. VSTO support for managed add-ins was introduced in 2005. It is clear that the strategic technology choice is managed code, and for Office add-ins it is VSTO. For a discussion of the main reasons why traditional managed add-ins (aka shared add-ins) are problematic – and how VSTO add-ins address these problems – see this MSDN article on the architecture of the original VSTO 2005 Outlook add-in support.
So, are shared add-ins dead? Not really: you still need shared add-ins in the following cases:
1. If you want (or need) a single add-in that targets multiple apps directly.
2. If you want (or need) to build in native code.
3. If you’re targeting Office versions prior to Office 2003.
4. If you’re targeting Office apps that VSTO doesn't support.
5. If you’re targeting non-Office apps that support the same COM add-in model (such as MapPoint).
6. If you want a very minimal runtime dependency apart from the CLR (and specifically want to avoid the overhead of the VSTO runtime).
Let’s examine each of these in turn. For #1, you can easily satisfy the requirement to target multiple hosts with VSTO add-ins by building simple app-specific shell add-ins which delegate all the real work to some common DLLs. This is likely to be a better architecture anyway – in terms of simplification of design, logical abstraction of functionality, functional separation of components, and component re-use.
For #2, it is true that developing in a native language such as C/C++ is more difficult (and more error-prone) than building in a managed language – and this is a significant factor in the success of VSTO, which only supports managed add-ins. However, there are several reasons why native code is often preferred, notably:
ú The organization may already have a large library of native domain-specific code that will be called by the add-in, and they don’t want to take the dev/test cost of calling out through p/invoke. The most commonly-quoted example is quants libraries used in Excel add-ins.
ú If the organization can completely control which add-ins are installed in the enterprise, they have the option to eliminate managed code from Office hosts. They might want to do this to avoid the admin overhead of deploying/managing the CLR, additional admin for managed code, and the perf (and working set) hit of loading the CLR into an Office process.
ú Apart from the up-front perf hit from loading the CLR, managed add-ins will generally perform slower than native add-ins – especially if they make chatty calls to the native host OM via the PIAs, because of the overhead inherent in COM interop.
You could argue that #3, #4 and #5 are really variations of the same constraint – the need to build an add-in for (a particular version(s) of) some (Office or non-Office) application that VSTO does not support. As we know (see here and here), you can morph a VSTO project for a supported application into a project for an unsupported one – but the key is that this is unsupported – and most enterprises and ISVs are justifiably reluctant to wander into unsupported territory.
#6 is almost a circular argument: you want to use a shared add-in because you don’t want to use the alternative (VSTO) add-in type. Nonetheless, it is a valid requirement. The VSTO runtime is relatively small (about 0.5 Mb runtime), but still bigger than the alternative. (The alternative “runtime” for managed add-ins is the COM shim, which is tiny in comparision to the VSTO runtime).
So, while issue #1 is not really a problem, the remaining 5 are. In mitigation, the landscape is changing. Specifically, you can expect to see VSTO supporting more and more applications in the future – certainly within the Office suite, and potentially outside also. Note, however, that this will not be retrospective – you’ll likely never see VSTO supporting apps prior to 2003, for example (this might technically work, but it wouldn’t be supported – because it is untested). Bottom line: there are cases where you cannot use VSTO and you must therefore use shared add-ins, and this will likely always be true.
Suppose you control your enterprise desktops to the extent that you control which add-ins are installed. Suppose, further, that you want to avoid the hit of loading the CLR at application startup. One way is to delay-load your managed add-ins. The registered LoadBehavior for an Office add-in governs how the add-in is loaded (surprise). Note these values are in decimal:
|
LB |
Meaning |
Status in COM Add-ins Dialog |
Behavior Description |
|
0 |
Unloaded |
Disconnected, Unloaded |
The add-in is not loaded when the application starts. It can be loaded through the COM Add-ins dialog box or programmatically, but reverts to “Unloaded” when the application closes. |
|
1 |
Loaded |
Disconnected, Loaded |
The add-in is not loaded when the application starts (despite the status of “Loaded”). It can be loaded through the COM Add-ins dialog box or programmatically, but reverts to “Loaded” when the application closes. |
|
2 |
Boot-Loaded |
Disconnected, Load at Startup |
The add-in is not loaded when the application starts. It can be loaded through the COM Add-ins dialog box or programmatically. Once the add-in is loaded, it remains loaded until it is explicitly unloaded, that is LoadBehavior is set to 3, and this status is persisted in the registry across sessions. |
|
3 |
Boot-Loaded |
Connected, Load at Startup |
The add-in is loaded when the application starts. It remains loaded until it is explicitly unloaded. |
|
8 |
Demand-Loaded |
Disconnected, Load on Demand |
The add-in will be loaded and connected when the host application requires it, eg, when a user clicks on a button that uses functionality in the add-in, that is LoadBehavior is set to 9. |
|
9 |
Demand-Loaded |
Connected, Load on Demand |
The add-in will be loaded and connected when the host application requires it, eg, when a user clicks on a button that uses functionality in the add-in. |
|
16 |
Connect First Time |
Connected, Load on Demand (currently loaded) |
The add-in loads as soon as the application starts the first time after the add-in is registered. Typically, the add-in creates a button or menu item for itself. The next time the user starts the application, the add-in is loaded on demand (LB=8), that is, it doesn't load until the user clicks the button or menu item associated with the add-in. This sets the LoadBehavior to 9. |
Let’s say you have a number of managed add-ins, and you want to be able to defer (or even completely avoid) loading them until you’re sure that they are needed in the current session. For example, let’s say that your Excel users sometimes work with workbooks that you care about – perhaps these workbooks have some custom property that identifies them as being part of some solution. Sometimes the users work on workbooks that are not part of any enterprise solution. If, during a session, the user only works on workbooks you don’t care about, you want to avoid loading any managed add-ins. In this way, the users avoid the perf hit of loading the CLR unless the custom functionality in your managed add-ins is actually required.
You can use the standard Office delay-load mechanism as noted in the LoadBehavior table above. The constraint here is that you’re dependent on some user action to notify Office that it needs to load the add-in, and that might not fit your requirements.
Another way you could achieve this is to build a native add-in that performs the test of whether or not to load the managed add-ins (and therefore, the CLR). For example, this could examine each workbook (or document in Word, presentation in PowerPoint, etc) that the user opens, to decide whether or not to load the managed add-ins.
The required operations for this mechanism are pretty simple. Each Office application exposes its collection of registered add-ins in a COMAddIns collection. In this collection, each add-in is represented by a COMAddIn object – this is true for all registered add-ins, regardless of whether the add-in is actually loaded or unloaded (and regardless of its LoadBehavior value). The COMAddIn interface exposes a number of properties, of which two are particularly interesting in this context:
· The Object property, which represents any arbitrary object that your add-in wants to expose for external automation. See my post on RequestComAddInAutomationService for more details of this.
· The Connect property, which represents the connected state of the add-in: true=connected, false=registered but disconnected. Note that for VSTO add-ins, if the add-in is not connected it’s also not loaded (and the runtime has not created an appdomain for it).
So, to defer or avoid loading the CLR, you can build a native add-in that conditionally sets the Connect property on your managed add-in(s).
To test this out, I created a Shared Add-in in C++. In the stdafx.h, I added #imports for the Office and Excel typelibs (my add-in targets Excel):
// Import the latest Office type library based on its registration.
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" \
named_guids, auto_search, auto_rename, \
rename("_IID_Adjustments", "Office_IID_Adjustments")
using namespace Office;
// Latest registered Excel typelib.
#import "libid:00020813-0000-0000-C000-000000000046" \
auto_search, auto_rename
using namespace Excel;
Then, I replaced the declaration of the generic IDispatch smart pointer that the shared add-in project gives you for the application object with an Excel-OM Application smart pointer, that is, changed this:
CComPtr<IDispatch> m_pApplication;
…to this:
Excel::_ApplicationPtr m_spExcel;
…and initialized it in the OnConnection method from the incoming IDispatch* (the smart pointer assignment performs a QI for me):
m_spExcel = pApplication;
Note that I could have used the IDispatch pointer and late binding to get the COMAddIns collection, but it’s just easier to use strong typing. The only other significant task was to provide behavior to connect and disconnect my target VSTO add-in(s). In this example, the VSTO add-in I want to control has a registered ProgID of “VstoExcelAddIn”:
// Get the VstoExcelAddIn from the COMAddIns collection.
Office::COMAddInPtr spCOMAddIn;
CComVariant vtItem("VstoExcelAddIn");
spCOMAddIn = m_spExcel->COMAddIns->Item(&vtItem);
// Toggle the connected state of the add-in.
VARIANT_BOOL bOldState = spCOMAddIn->Connect;
if (bOldState == VARIANT_TRUE)
{
spCOMAddIn->Connect = VARIANT_FALSE;
}
else
{
spCOMAddIn->Connect = VARIANT_TRUE;
}
Of course, you still have to write the code that determines whether or not you want to connect/disconnect the add-in (and potentially, which add-in or add-ins you want to control). You might do this based on some custom document property – and that would require you to sink the WorkbookOpen/WorkbookBeforeClose events, and possibly the WindowActivate/Deactivate events. Or, it might be based on the current user account name and/or domain. Or, on the day of the week, or any other arbitrary condition.
So, if you want to control VSTO add-ins, you can simply build a native add-in to control them, as described above. If, on the other hand, you want to control non-VSTO managed add-ins, you could eithr use a native add-in in the same way – or alternatively, you could build this controlling functionality into your native shim. Note that this technique is not restricted to add-ins – you can build the same functionality into any of the native shims that the COM Shim Wizard supports – including managed smart tags, real-time data components, and automation add-ins.
Perusing the forums over the last few months, it’s clear that there’s some confusion about the various VSTO loader and runtime components. At the time of writing, there are 4 versions of VSTO in existence, including 4 sets of design-time tooling and 3.5 versions of the VSTO runtime. I say 3.5 versions, because the VSTO 2005 SE runtime was an in-situ replacement for the VSTO 2005 runtime, and both are considered VSTO v2 (2005 SE was purely additive to 2005). Other versions – that is, VSTO 2003 (v1) and VSTO 2008 (v3) – are SxS with v2.
Right now, each VSTO solution runs against the version of the VSTO runtime that it was built against, regardless of the version of Office that is hosting it. It can be confusing to figure out which version will be used, so here’s a breakdown. The 4 versions of VSTO in existence at the time of writing are:
· VSTO 2003 (VSTO v1 runtime), which supported doc-level customizations for Excel and Word 2003 only, in a very simple fashion.
· VSTO 2005 (VSTO v2 runtime), which provided very much broader and deepter runtime support for Excel/Word 2003 doc-level customizations, and introduced app-level add-in support for Outlook 2003.
· VSTO 2005 SE (VSTO v2 runtime), which maintained the v2 runtime support for Excel/Word 2003 doc-level customizations, and replaced the app-level add-in support – extending it to cover Excel, Outlook, PowerPoint, Visio and Word 2003, and Excel, InfoPath, Outlook, PowerPoint, Visio and Word 2007.
· VSTO 2008 (VSTO v3 runtime), which maintained the v2 runtime support for Excel/Word 2003 doc-level customizations, and added v3 runtime support for Excel/Word 2007 doc-level customizations. Also maintained the v2 runtime support for app-level add-ins for Excel, Outlook, PowerPoint, Visio and Word 2003, and added Project 2003. Also added v3 runtime support for app-level add-ins for Excel, InfoPath, Outlook, PowerPoint, Project, Visio and Word 2007. Plus, added design-time support for SharePoint 3.0 Workflow.
The runtime versions are used as follows, first the doc-level solution types:
· A VSTO 2003 v1 doc-level solution will always run with the VSTO v1 runtime. You could only create a VSTO 2003 solution for Excel/Word 2003, but the solution should continue to run without change in Excel/Word 2007 – and it will continue to use the VSTO v1 runtime.
· A VSTO 2005 v2 doc-level solution will always run with the VSTO v2 runtime. There’s no such thing as a VSTO 200 SE doc-level solution, because VSTO 2005 SE was additive to VSTO 2005, adding only app-level add-in support. Again, a VSTO 2005 doc-level solution built against Excel/Word 2003 should continue to run unchanged in Excel/Word 2007 and will continue to use the VSTO v2 runtime.
· A VSTO 2008 doc-level solution for Excel/Word 2003 will use the VSTO v2 runtime. It should also continue to run without change in Excel/Word 2007 and will continue to use the VSTO v2 runtime.
· A VSTO 2008 doc-level solution for Excel/Word 2007 will use the VSTO v3 runtime. It will not run in Excel/Word 2003.
Similar patterns hold true for app-level add-ins:
· A VSTO 2005 v2 Outlook add-in will run against the VSTO 2005 v2 runtime so long as VSTO 2005 SE is not installed. If VSTO 2005 SE is installed, then the add-in will run against the VSTO 2005 SE v2 runtime.
· A VSTO 2005 SE v2 add-in for Excel, Outlook, PowerPoint, Visio or Word 2003 will run against the VSTO 2005 SE v2 runtime. It will not run if the VSTO 2005 SE runtime is not installed. It will continue to run without change in Excel/Outlook/PowerPoint/Visio/Word 2007, and it will continue to use the VSTO 2005 SE v2 runtime.
· A VSTO 2005 SE v2 add-in for Excel, InfoPath, Outlook, PowerPoint, Visio or Word 2007 will run against the VSTO 2005 SE v2 runtime. It will not run if the VSTO 2005 SE runtime is not installed. It will not run in Office 2003 because it requires the Office 2007 PIAs which are not designed to be used with Office 2003.
· A VSTO 2008 add-in for Excel, Outlook, PowerPoint, Project, Visio or Word 2003 will run against the VSTO 2005 SE v2 runtime. It will not run if the VSTO 2005 SE runtime is not installed. It will continue to run without change in Excel/Outlook/PowerPoint/Project/Visio/Word 2007, and it will continue to use the VSTO 2005 SE v2 runtime.
· A VSTO 2008 add-in for Excel, InfoPath, Outlook, PowerPoint, Project, Visio or Word 2007 will run against the VSTO 2008 v3 runtime. It will not run if the VSTO 2008 runtime is not installed. It will not run in Office 2003 because it requires the Office 2007 PIAs which are not designed to be used with Office 2003.
The different versions of the VSTO runtime include different loader components. The load sequence therefore varies according to the version of the solution. For doc-level customizations, the load sequences are:
· VSTO 2005: Otkloadr.dll à VSTOLoader.dll à VSTORuntime.dll à solution
· VSTO 2005 SE Office 2003: Otkloadr.dll à VSTOLoader.dll à VSTORuntime.dll à solution
· VSTO 2005 SE Office 2007: Otkloadr.dll à VSTOLoader.dll à VSTORuntime.dll à solution
· VSTO 2008 Office 2003: Otkloadr.dll à VSTOLoader.dll à VSTORuntime.dll à solution
· VSTO 2008 Office 2007: VSTOEE.dll à VSTOLoader.dll à solution
The load sequences for app-level add-ins are:
· VSTO 2005: AddinLoader.dll à solution
· VSTO 2005 SE Office 2003: AddinLoader.dll àVSTORuntime.dll à solution
· VSTO 2005 SE Office 2007: VSTOEE.dll àAddInLoader.dll à VSTORuntime.dll à solution
· VSTO 2008 Office 2003: AddInLoader.dll à VSTORuntime.dll à solution
· VSTO 2008 Office 2007: VSTOEE.dll à VSTOLoader.dll à solution
Why do we have so many runtime versions, you might ask? Why is it so seemingly complicated? Well, the runtimes are designed for the opposite purpose – the intention is to make life simple for users – users should not need to worry about which version is used, and we have multiple versions to ensure that all solutions will continue to run without change in the face of new versions of Office. A major factor in this is the requirement that a deployed solution should continue to work without any user/admin intervention even though the user might upgrade the version of Office on their machine. They might have multiple solutions deployed which were built over time against multiple versions of VSTO – and all of them should just continue to work.
Everyone knows you can build document-level Office solutions and you can build application-level Office add-ins. Suppose your requirements dictate that you build a solution that uses both techniques – can this be done? First, let’s pause and consider whether this is a good idea in the first place, and whether you could re-architect to use only one or the other.
In most cases, the requirements for a solution can be met either with doc-level solutions or app-level add-ins. The Office application’s COM object model is entirely available to both types of solution. In some cases, there will be clear advantages to one or the other approach. For example, the VSTO-enhanced doc-level host-item controls (eg, XmlNodes and Content Controls in Word – or, ListObjects and NamedRanges in Excel) are only available in doc-level solutions. In other cases, you could do it either way. For example, suppose you have a need for some control UI in a custom task pane – you could use the doc-level Actions Pane, or you could use the app-level Custom Task Pane. Suppose you need Ribbon customization – again, you could do this either with a doc-level solution or with an app-level add-in. The same is true for custom CommandBars in older versions of Office.
On the other hand, there will be some contexts where the only possible solution requires the use of both techniques. Consider this scenario, for example:
· Your solution needs to work with users who have Word 2003 and with other users who have Word 2007.
· A user must be able to work with the solution in one version, then hand the document to another user who might be using the other version, who then hands it back for further work. In other words, the same document must be editable in both versions, it must be round-trippable, and the VSTO customization must work in both versions.
· The solution requires custom CommandBars when running in Word 2003, and custom Ribbon when running in Word 2007.
· The solution requires some form of Custom Task Pane or Actions Pane.
These requirements impose significant constraints on the solution design, specifically:
· The solution must use the Word97-2003 binary file format, because the VSTO v3 runtime that works with the new Open XML file format does not work with Word 2003.
· You cannot use doc-level Ribbon customization, because this only works with the new file format. So, you must use app-level Ribbon customization.
· You cannot use an app-level Custom Task Pane, because it must work the same in Word 2003 which does not support Custom Task Panes. So, you must use the doc-level Actions Pane.
To meet these seeminly conflicting requirements, you’d have to design the solution to use both doc-level and app-level components:
· Build an app-level add-in, which will work in both Word 2003 and 2007. This would include conditional code to determine which version of Word the solution is running in, and set up either a Ribbon customization or custom CommandBars accordingly.
· Build a doc-level customization, targeting the old binary file format in Word 2003 – which will also work in Word 2007, so you can have a doc-level ActionsPane.
· Remaining business logic could be put in either place: either in the doc-level customization or in the app-level add-in. If you need closely integrated processing of VSTO doc-level host-item control wrappers, this obviously goes in the doc-level customization. Any other work could go in either place.
· Connect the doc-level customization with the app-level add-in through shared logical interfaces. That is, the doc code will include an object that implements some interface, say IDocumentCommands, that is known to the add-in; and the add-in will include an object that implements some interface, say IAddInCommands, that is known to the doc code. These interfaces should be defined in a separate assembly that is then re-used by both the doc customization and the add-in.
· The runtime hook-up can use the fact that all app-level add-ins are exposed through the host application’s COMAddIns collection. Each add-in can expose any arbitrary functionality by setting the COMAddIn.Object property to some arbitrary object. In a sensible system, you would set this property to an object that implements an interface that is known to both the doc-level customization and the app-level add-in, say IAddInCommands.
· Calls from the doc-level customization to the app-level add-in will therefore go through this object, via standard CLR COM interop.
· The Office COM object models do not expose any COMAddIns equivalent for doc-level customizations. That means that the doc-level code must make itself available in a custom manner, and without the benefit of CLR COM interop.
· Therefore, the solution requires that the doc-level code gets hold of the add-in’s exposed object via the COMAddIns collection, and hands off to the add-in some exposed doc-level object. From this point, calls in the other direction – that is, from the app-level add-in to the doc-level customization will go through remoting, via the shared (IDocumentCommands) interface.
Here’s an overview of the design:

Let’s break this down. First, we define the two interfaces in a separate class library. Note that the IDocumentCommands interface will be implemented in the doc code, and the implementing object will be used via remoting – so this does not need to be COM-visible. On the other hand, the IAddInCommands will be implemented by the object that the add-in sets into the COMAddIn.Object property, and will be used via COM interop – and therefore needs to be a COM-visible dual interface:
namespace DocumentAddInInterfaces
{
public interface IDocumentCommands
{
void InsertText(string s);
}
[ComVisible(true)]
[Guid("158CF0BC-16F3-49be-A4DD-E3A81283C7A0")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddInCommands
{
void RegisterDocument(IDocumentCommands d);
void InsertText(string s);
}
}
When the add-in loads, it constructs some arbitrary object that it wishes to expose. This object implements an interface (IAddInCommands) that is known to both the add-in and the doc customization. This interface offers two methods: RegisterDocument, which allows the doc-level code to register itself with the add-in; and InsertText, which inserts some text in the document:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class AddInCommands : IAddInCommands
{
private Word.Application wordApplication;
public AddInCommands(Word.Application w)
{
wordApplication = w;
}
public void RegisterDocument(IDocumentCommands d)
{
Globals.ThisAddIn.documentCommands = d;
}
public void InsertText(string s)
{
object missing = Type.Missing;
Word.Range r = wordApplication.ActiveDocument.Range(
ref missing, ref missing);
r.InsertAfter(
String.Format("Add-in.InsertText: {0}{1}",
s, Environment.NewLine));
}
}
The add-in sets this object as the value of the Object property in the COMAddIn object that represents this add-in in the COMAddIns collection of the host application. In VSTO add-ins, this is done by overriding the RequestComAddInAutomationService method:
public partial class ThisAddIn
{
internal IDocumentCommands documentCommands;
public IAddInCommands addInCommands;
protected override object RequestComAddInAutomationService()
{
if (addInCommands == null)
{
addInCommands = new AddInCommands(this.Application);
}
return addInCommands;
}
}
When the document loads (at some point after the add-in is loaded), it gets hold of the application’s COMAddIns collection, and the specific COMAddIn object that represents the add-in it wants. From this, it gets the add-in’s exposed object that implements IAddInCommands. The first thing it does with this object is to call back on its RegisterDocument method. This is the method that allows the doc code to pass an arbitrary object to the add-in code, thereby establishing the two-way communication:
public partial class ThisDocument
{
public IDocumentCommands documentCommands;
internal IAddInCommands addInCommands;
private void ThisDocument_Startup(object sender, System.EventArgs e)
{
SimpleControl simpleControl = new SimpleControl();
this.ActionsPane.Controls.Add(simpleControl);
try
{
documentCommands = new DocumentCommands(this.Application);
object addInProgId = "IntegrationTestAddIn";
addInCommands =
(IAddInCommands)
this.Application.COMAddIns.Item(ref addInProgId).Object;
addInCommands.RegisterDocument(documentCommands);
simpleControl.btnCallAddIn.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
Note that the code above assumes we have a SimpleControl custom UserControl that we’re placing in the doc-level Actions Pane. This SimpleControl offers a button that the user can click.
The doc object implements another known interface (IDocumentCommands) which has one method, InsertText. This object will be used via remoting, so we’ll make it MBRO:
internal class DocumentCommands :
System.MarshalByRefObject, IDocumentCommands
{
private Word.Application wordApplication;
public DocumentCommands(Word.Application w)
{
wordApplication = w;
}