In general, it is important that any code in a managed Office add-in should execute on the main UI thread. The reason for this is that there are several components that simply will not work when executed from any other but the main UI thread – examples include calls on WinForms controls, and calls into the VSTO runtime. Fortunately, in most normal circumstances, your add-in code will run on the main UI thread.

However, there is one scenario where you need to be more vigilant. Recall that you can expose your add-in through the COMAddIn.Object property of the COMAddIn object that represents your add-in in the COMAddIns collection, either directly (in a non-VSTO add-in) or through VSTO’s RequestComAddInAutomationService mechanism (documented here). If this object is consumed in-proc (for example, by another add-in in the same process, or a VSTO doc-level customization, or VBA macro), there should be no problem. On the other hand, if the object is consumed by an out-of-proc client, there will be a problem.

By default, calls coming from out-of-proc clients will not be executed on the main UI thread. This is because from COM’s perspective all managed objects by default are thread-neutral. One consequence of this is that COM will not attempt to switch threads to execute calls into such objects, and since cross-process COM calls will come in on an arbitrary RPC thread, COM decides to make the call right on this thread.

This, of course, is not the behavior that we want. What we really want is to tell COM that our managed object lives in a Single Threaded Apartment (STA), because then COM will marshal the call onto the thread the object was created on (which, for Office add-ins, will be the main UI thread). To achieve this, you can simply derive your exposed add-in object from System.Runtime.InteropServices.StandardOleMarshalObject. This class is documented here.

The underlying reasons for this behavior go back to the original design of COM and OLE (OLE being a layer on top of COM that supports various UI mechansims). Essentially, COM objects involved with UI programming nearly always require a single-threaded apartment. Multi-threaded apartments were designed on the assumption that there was no UI. Further details here.

For an example of where using StandardOleMarshalObject will make a difference, let’s suppose I have an Excel add-in (called “ComServiceOleMarshal”) that exposes an AddinUtilities object:

[ComVisible(true)]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

public interface IAddinUtilities

{

    void DoSomething();

}

 

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities : IAddinUtilities

{

    public void DoSomething()

    {

        Globals.ThisAddIn.CreateNewTaskPane();

    }

}

The main ThisAddIn object in the add-in instantiates this AddinUtilities object, exposes it via RequestComAddInAutomationService, and also exposes a CreateNewTaskPane method which the AddinUtilities object calls. The CreateNewTaskPane method uses the VSTO CustomTaskPanes.Add method, which internally invokes Office code in order to create a new task pane. Thus, we’ve set things up such that any call into the AddinUtilities method will result in a call into the Office host app.

public partial class ThisAddIn

{

    private AddinUtilities addinUtilities;

 

    protected override object RequestComAddInAutomationService()

    {

        if (addinUtilities == null)

        {

            addinUtilities = new AddinUtilities();

        }

        return addinUtilities;

    }

 

    internal void CreateNewTaskPane()

    {

        Microsoft.Office.Tools.CustomTaskPane taskPane =

            this.CustomTaskPanes.Add(new UserControl(), "New TaskPane");

        taskPane.Visible = true;

    }

}

If you write client code to consume this AddinUtilities object in an in-proc component, such as another Excel add-in, a VSTO doc-level customization, or from VBA, everything should work OK:

'In-proc VBA

Private Sub CommandButton1_Click()

    Dim addin As Office.COMAddIn

    Dim automationObject As Object

    Set addin = Application.COMAddIns("ComServiceOleMarshal")

    Set automationObject = addin.Object

    automationObject.DoSomething

End Sub

However, if you write an external automation client to invoke the AddinUtilities object, things will go horribly wrong. For example, here’s a simple managed console client:

static void Main(string[] args)

{

    Excel.Application excel = new Excel.Application();

    excel.Visible = true;

 

    Console.WriteLine("press a key to invoke the AddinUtilities object");

    Console.ReadLine();

 

    object addinName = "ComServiceOleMarshal";

    Office.COMAddIn addin = excel.COMAddIns.Item(ref addinName);

    ComServiceOleMarshal.IAddinUtilities utils =
        (ComServiceOleMarshal.IAddinUtilities)addin.Object;

    utils.DoSomething();

 

    Console.WriteLine("press a key to close Excel");

    Console.ReadLine();

}

When this code attempts to call through to the AddinUtilities object, we’ll typically get an E_NOINTERFACE error like this:

System.InvalidCastException: Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.VisualStudio.Tools.Office.Runtime.Interop.ICustomTaskPaneSite'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{3CA8CD11-274A-41B6-A999-28562DAB3AA2}'failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).   at ComServiceOleMarshal.IAddinUtilities.DoSomething()   at ConsoleTest.Program.Main(String[] args) in C:\Temp\ComServiceOleMarshal\ConsoleTest\Program.cs:line 29

This message is pretty obscure. What’s happening is that the CustomTaskPanes.Add method uses the ICustomTaskPaneSite interface to call into the VSTO runtime. Since the VSTO runtime is implemented as an STA COM object,  the call has to go through the COM marshaling mechanism. However, the ICustomTaskPaneSite interface has not been designed to be marshaled, and so the entire call fails.  To fix all this, you can simply derive the AddinUtilities class from StandardOleMarshalObject, and rebuild:

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities :

    StandardOleMarshalObject,

    IAddinUtilities

{

    public void DoSomething()

    {

        Globals.ThisAddIn.CreateNewTaskPane();

    }

}

Now calls to the exposed object should work correctly for out-of-proc clients also. Source code for the add-in and the client are attached to this post. Many thanks to Misha for helping me figure out the exact behavior here.