Welcome to MSDN Blogs Sign in | Join | Help

Andrew Whitechapel

Visual Studio, Office and other Nonsense
VSTO Add-ins, COMAddIns and RequestComAddInAutomationService

The COMAddIns property is a collection of COMAddIn objects exposed by Office applications that support COM add-ins. The COMAddIn interface defines a small number of methods/properties, such as the ProgId of the add-in and the Connect state. It also defines an Object property. By default, the value of the Object property is null. An add-in can set this value itself, and the purpose is to expose this add-in’s functionality to external callers. VSTO supports this mechanism through a new virtual method on the AddIn base class, RequestComAddInAutomationService. Most developers will never use this service, and its an example of one of the little things that VSTO does to support the widest range of add-in developers. Here’s how it works.

In your VSTO add-in class, you decide which methods you want to expose to external callers, and wrap these methods in a ComVisible class. Then, you override the RequestComAddInAutomationService virtual method to return an instance of this class. For example, we’ll expose a DisplayMessage method. The calls to this method will be made via automation, so I’m defining an automation (dual) interface to define the method:

[ComVisible(true)]

[Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

public interface IAddinUtilities

{

    void DisplayMessage();

}

 

I can also define a class that will implement the interface to perform some suitable operation in the method:

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities : IAddinUtilities

{

    public void DisplayMessage()

    {

        MessageBox.Show("Hello World");

    }

}

 

Then, in my add-in class, I override RequestComAddInAutomationService to return an instance of this class. As you can see, the programming model for this method is very similar to the generic RequestService method that VSTO uses for the new extensibility interfaces (although of course the underlying mechanism is completely different):

public partial class ThisAddIn

{

    private AddinUtilities addinUtilities;

 

    protected override object RequestComAddInAutomationService()

    {

        if (addinUtilities == null)

        {

            addinUtilities = new AddinUtilities();

        }

        return addinUtilities;

    }

}

 

Then, I can create a suitable Office document and insert some VBA to call this exposed method. So, if my add-in is an Excel add-in, I can create an XLSM (macro-enabled) workbook and Alt-F11 to get the VBA editor up. I’ll put a CommandButton on the worksheet, and code it to call the DisplayMessage method:

Private Sub CommandButton1_Click()

    Dim addin As Office.COMAddIn

    Dim automationObject As Object

    Set addin = Application.COMAddIns("ExcelAddinService")

    Set automationObject = addin.Object

    automationObject.DisplayMessage

End Sub

 

From the VBA code, you can see that my add-in is registered as “ExcelAddinService”. All I’m doing is fetching the COMAddIn object that represents this particular add-in in the COMAddIns collection (specified by ProgId). Then, I’m fetching the Object property of this add-in, and invoking the exposed method. If you’re following along, joining the dots as it were, you can infer that the VSTO runtime takes the return value from the RequestComAddInAutomationService method in the add-in to set the COMAddIn::Object property to the instance of the AddinUtilities class defined in the add-in.

Note that my VBA is using late binding – I don’t have a reference to the add-in at design-time. If the add-in exposed many methods with varying signatures, it might be worth adding a reference to it in the VBA editor.

So, to show this, I’ll expand my add-in to expose a second method, this one takes a couple of parameters and uses them to interact with the active Worksheet.

[ComVisible(true)]

[Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

public interface IAddinUtilities

{

    void DisplayMessage();

    void SetCellValue(String cellAddress, object cellValue);

}

 

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities : IAddinUtilities

{

    public void DisplayMessage()

    {

        MessageBox.Show("Hello World");

    }

 

    public void SetCellValue(String cellAddress, object cellValue)

    {

        Excel.Worksheet sheet = (Excel.Worksheet)

            Globals.ThisAddIn.Application.ActiveSheet;

        Excel.Range cell = sheet.Cells.get_Range(

            cellAddress, Type.Missing);

        cell.Value2 = cellValue;

    }

 

}

 

In my VBA, I can add a reference to the AddinService add-in, so that I get design-time intellisense. I’ll put a second CommandButton on the worksheet, and code it to call the SetCellValue method:

Private Sub CommandButton2_Click()

    Dim addin As Office.COMAddIn

    Dim utilities As ExcelAddinService.addinUtilities

    Set addin = Application.COMAddIns("ExcelAddinService")

    Set utilities = addin.Object

    Call utilities.SetCellValue("a1", 456.78)

End Sub

 

I can even write a Windows Forms app to automate Excel externally, and invoke the exposed add-in methods. For example, this is a Windows Forms app with 2 buttons – I launch Excel when the form loads, and cache the Application and the add-in Object. Then, I call one of the exposed methods in each button Click handler:

public partial class WinTestAddinServiceForm : Form

{

    public WinTestAddinServiceForm()

    {

        InitializeComponent();

    }

 

    private Excel.Application excel;

    private ExcelAddinService.IAddinUtilities utils;

 

    private void WinTestAddinServiceForm_Load(object sender, EventArgs e)

    {

        // Launch Excel, make it visible, and ensure there is

        // at least one sheet.

        excel = new Excel.Application();

        excel.Visible = true;

        excel.Workbooks.Add(Excel.XlSheetType.xlWorksheet);

 

        // Fetch the add-in we want to exercise, and cache

        // its exposed object.

        object addinName = "ExcelAddinService";

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

        utils = (ExcelAddinService.IAddinUtilities)addin.Object;

    }

 

    private void WinTestAddinServiceForm_FormClosed(

object sender, FormClosedEventArgs e)

    {

        // Clean up all Excel object references.

        utils = null;

        excel = null;

        GC.Collect();

        GC.WaitForPendingFinalizers();

        GC.Collect();

        GC.WaitForPendingFinalizers();

    }

 

    private void displayMessage_Click(object sender, EventArgs e)

    {

        utils.DisplayMessage();

    }

 

    private void setCellValue_Click(object sender, EventArgs e)

    {

        utils.SetCellValue("a1", 123.45);

    }

}

 

As you can see, I’ve obviously got a reference to the ExcelAddinService assembly in this project – that’s only so that I can use the IAddinUtilities interface. Realistically of course, the interface would be best defined in an assembly separate from the add-in.

This feature seems like a small thing, but actually its part of a wider strategy to support the idea of Office as a true development platform. That idea implies at least some minimal level of interconnectivity support between various custom pieces in a solution.

 

Posted: Monday, January 15, 2007 4:05 PM by andreww

Comments

Qanuc said:

Hi!

I tried to use your code to builf an Outlook AddIn. The whole things works great until it reaches the line

'utils = (ExcelAddinService.IAddinUtilities)addin.Object;'

An Exception is thrown because addin.Object is a System.__ComObject and cannot be cast to my interface.

Any idea about this?

Regards

 M

# January 18, 2007 7:56 AM

andreww said:

Hi M - a few questions:

Did you return an instance of the AddinUtilities class in the RequestComAddInAutomationService method?

How did you attribute the class?

Did you make the assembly ComVisible?

Did you mark it as Register for COM interop?

Thanks

Andrew

# February 1, 2007 3:00 PM

Conor said:

I am also seeing the casting error when I try to write a C# (early-bound) program to replicate what I've seen here.  The late-bound vbs version works fine.  To answer your questions:

1. Yes, I returned an instance of the AddinUtilities class in RequestComAddInAutomationService.

2. attributes should match your example:

   [ComVisible(true)]

   [Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]

   [InterfaceType(ComInterfaceType.InterfaceIsDual)]

   public interface IAddinUtilities

   {

       void DisplayMessage();

   }

   [ComVisible(true)]

   [ClassInterface(ClassInterfaceType.None)]

   public class AddinUtilities : IAddinUtilities

   {

       public void DisplayMessage()

       {

           MessageBox.Show("Hello World");

       }

   }

3. Your example only has methods marked as ComVisible.  

I have tried marking the add in project to be comvisible such that reflector shows:

// Assembly ExcelAddIn1, Version 1.0.0.0

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyTitle("ExcelAddIn1")]

[assembly: Debuggable(DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.EnableEditAndContinue | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.Default)]

[assembly: CompilationRelaxations(8)]

[assembly: AssemblyDescription("")]

[assembly: Guid("8d818b4d-3f48-4e88-9e27-4559992a7325")]

[assembly: RuntimeCompatibility(WrapNonExceptionThrows=true)]

[assembly: ComVisible(true)] <==== HERE

[assembly: AssemblyTrademark("")]

[assembly: AssemblyConfiguration("")]

[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: AssemblyCopyright("Copyright \x00a9  2007")]

[assembly: AssemblyCompany("")]

[assembly: AssemblyProduct("ExcelAddIn1")]

4. Beyond clicking "Make assembly COM-Visible" in the Build property Application..Assembly Information, is there another step to take?

Thanks,

Conor

# March 12, 2007 3:24 PM

Mike Kelley said:

The example code provided works fine for C# early binding - you have to set your add-in project's Build settings to "Register for COM Interop" - as Andrew stated above.

You do not need to set the 'make assembly com visible' as the decorations take care of the needed interfaces/classes.

It will throw casting exceptions if you do not have this build option set.

# March 12, 2007 8:02 PM

Scott said:

In my ThisAddIn Class , I cann't override RequestComAddInAutomationService method,what's the problem.    

Thanks & Regards

# April 1, 2007 7:00 AM

Telmo said:

Scott,

I had also your problem , the solution i found was to change my project to a vb.net one,  that solved my problem.

Now i have a problem each time i change the add  i have to remove and add the updated addin is this correct or am i missing something ?

Thanks in advance

TC

# April 5, 2007 5:47 AM

Bartolomeo said:

Hi,

How to implement similar functionality in shimmed add-in?

# September 26, 2007 7:27 AM

markovich said:

Did you make the assembly ComVisible?

Did you mark it as Register for COM interop?

# October 7, 2007 3:58 PM

Denis_Rutovitz said:

To Andrew Whitehead:

your example is just what I need, except that I want to write the Addin in MS Studio 2005 VB, not in C++ (because (1) its 10 years since I last used C, and (2) I 'm trying to make an addin of a body of code, forms etc written in VBA)

Is it possible? Any suggestions ?

# December 3, 2007 7:27 AM

Denis_Rutovitz said:

Apologies, I should have said Andrew Whitechapel, not Andrew Whitehead.

# December 3, 2007 8:46 AM

Oleg Krupnov said:

Is there a way to avoid any VBA code and call managed routines directly? You see, I need to connect it to a KeyBinding command - a string kind of "ModuleName.MethodName", but instead of referencing to the corresponding VBA Module and Sub, I need to reference directly to my managed add-in (or customization) method. Is this possible? Having to write any VBA code would cause additional macro security issues and generally nullifies the idea. Thanks!

# January 25, 2008 12:55 PM

Adam said:

Thanks for the great article, one question though. I've put together an addon using your example here for Outlook 2003.

What I'd like to do is start outlook through automation ie:

dim app as new Outlook.Application()

Then go ahead and read in the addin's Object refrence.

The problem is this: It works perfectly if outlook is already running, but if outlook is not running the Object always returns null.

I'm guessing that my addin is not being loaded because Outlook.exe was started with automation, so the call to RequestComAddinAutomationService never happened.

Do you know is this is the case? If so, do you know a work around, or an update to correct this behavior?

Again, appriciate your work in writing this, it was a real help.

# February 12, 2008 11:29 AM

Steve Booth said:

I would be really grateful if you could describe how to overcom the early binding issue when calling an addin from .Net

I can set the addin object to a .Net object and then call using late binding but i cannot for the life of my work out how to cast/set the addin object to my class created in the .Net addin

I am using all the techniques described above. The addin is for Outlook 2003.

# February 20, 2008 5:57 AM

andreww said:

Adam - apologies for taking so long to reply. For some reason the email notifications from this post stopped coming to me.

Anyway, this works fine for me with Outlook being externally automated. The only caveat is that when you first launch Outlook, it will take some time to perform housekeeping before you can be sure that all add-ins are actually loaded.

So, you need to wait a reasonable period of time before attempting to retrieve your add-in. I'm not sure there's a suitable event that Outlook fires that you could use - if not, you'll have to figure out some other workaround.

# April 9, 2008 10:43 PM

andreww said:

Steve - apologies to you also for the delay.

I'm not sure what you mean by the "early binding issue". Is this the same problem that Conor had? If so, the answer is the same one Mike Kelley gave - that is, make sure you have your project set to Register for COM interop.

# April 9, 2008 10:46 PM

Paul Berridge said:

Like Oleg Krupnov I want to avoid any VBA macros and link a keyboard shortcut to a comvisible method. Any idea how this can be done?

Example:

Private Sub ThisAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup

       ' Start of VSTO generated code

       Me.Application = CType(Microsoft.Office.Tools.Excel.ExcelLocale1033Proxy.Wrap(GetType(Excel.Application), Me.Application), Excel.Application)

       ' End of VSTO generated code

       Me.Application.OnKey("^f", "ExcelAddIn2.tlb!HelloWorld")

   End Sub

... and HelloWorld() is a comvisible function like this ...

Imports System.Runtime.InteropServices

<ClassInterface(ClassInterfaceType.AutoDual), ProgId("ExcelAddIn2.ComClass1"), ComVisible(True)> _

Public Class ComClass1

   Public Function HelloWorld() As Boolean

       MessageBox.Show("HelloWorld")

       Return True

   End Function

End Class

# April 14, 2008 4:09 AM

andreww said:

Paul/Oleg

Unfortunately, both Excel's OnKey and Word's KeyBindings are restricted to VBA macros only: you cannot directly use these to invoke managed code.

# April 14, 2008 7:50 PM

Terry Aney said:

So I attempted to do what your last comment said about putting the interface a seperate assembly.  When I do that, I only have following:

namespace BTR.Extensibility.Excel.Interfaces

{

[ComVisible( true )]

[InterfaceType( ComInterfaceType.InterfaceIsDual )]

public interface IAddinUtilities

{

void TestArchitecture( int count );

}

}

However when I try to COM register this, I get an error:

RegAsm : warning RA0000 : No types were registered

Any ideas?

# April 19, 2008 3:31 AM

Terry Aney said:

Just to clarify...I'm having early binding cast problems as well.

Currently I have 3 projects:

1) An Add-in project with essentially ThisAddin.cs and AddinUtilities.cs.

2) An inteface assembly/project with only IAddinUtilities.cs.

3) A Windows form application for the test host.  

Both the test host and the add-in both reference the interfaces assembly.

No assemblies have 'register for com interop' checked as I'm manually calling regasm after placing the assemblies in the appropriate directory (c:\prog files\...).  I plan on having my setup project do this, so I don't want to have it 'automatically' done by VS.

So I guess my question is, even given your setup above, does the IAddinUtilities interface show up in the registry anywhere?  The warning about 'no types were registered' above that I wrote happened on manual regasm calls as well as VS calls due to 'register for com interop'.

After regasm'ing both my add-in and interface assemblies, I have the following present in my registry (don't think I've missed anything):

[HKEY_CLASSES_ROOT\BTR.Extensibility.Excel.RBL.AddInUtilities]

@="BTR.Extensibility.Excel.RBL.AddInUtilities"

[HKEY_CLASSES_ROOT\BTR.Extensibility.Excel.RBL.AddInUtilities\CLSID]

@="{9E5B4D63-4365-30A5-AA35-07B926BECDA5}"

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}]

@="BTR.Extensibility.Excel.RBL.AddInUtilities"

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}\Implemented Categories]

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}\InprocServer32]

@="mscoree.dll"

"ThreadingModel"="Both"

"Class"="BTR.Extensibility.Excel.RBL.AddInUtilities"

"Assembly"="BTR.Extensibility.Excel.RBL.2003, Version=1.0.0.0, Culture=neutral, PublicKeyToken=824d1f6daf83e0eb"

"RuntimeVersion"="v2.0.50727"

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}\InprocServer32\1.0.0.0]

"Class"="BTR.Extensibility.Excel.RBL.AddInUtilities"

"Assembly"="BTR.Extensibility.Excel.RBL.2003, Version=1.0.0.0, Culture=neutral, PublicKeyToken=824d1f6daf83e0eb"

"RuntimeVersion"="v2.0.50727"

[HKEY_CLASSES_ROOT\CLSID\{9E5B4D63-4365-30A5-AA35-07B926BECDA5}\ProgId]

@="BTR.Extensibility.Excel.RBL.AddInUtilities"

Any information would be appreciated.

# April 19, 2008 3:57 AM

andreww said:

Terry - the regasm error message you're getting is because you can't regasm an assembly which doesn't contain any createable types. An interface is not a createable type. So, you'd need to declare a class in your interface assembly that implements the interface (as in the original example in my post). This class could either be the real class used by your code, or it could simply be a dummy placeholder with 'do nothing' methods that will never get instantiated but serves to allow regasm to register the interface.

# April 19, 2008 2:24 PM

Terry Aney said:

Thanks for the info.  Either I'm not doing something right or there are other problems.  Here's where I'm at:

a) VSTOAddIn.dll - in RequestComAddInAutomationService, creates and returns AddinUtilities class.

b) I've moved the IAddinUtilities and AddinUtilities interface/class back into the VSTOAddIn.dll to attempt to mimic your sample exactly and both are marked as COMVisible.  Note: the code is pasted below.

c) Automation Client.exe - VSTOAddIn.dll and tries to cast to early bound type:

d) My registry still looks about the 'same'.  In that I only have AddinUtilities in registry and not the IAddinUtilities (is that ever supposed to show up?  I'm assuming yes).

Let me know if it is worth sending you a sample project offline and if so, if/when we resolve issue, I could post a textual representation of the solution on your blog for others to see?

Here's the relevant code:

(from ThisAddin.cs)

protected override object RequestComAddInAutomationService()

{

 if ( addinUtilities == null )

 {

   addinUtilities = new AddInUtilities();

 }

 return addinUtilities;

}

(This is entire my 'Automation.dll' code)

namespace BTR.Extensibility.Excel.RBL.Automation

{

 [ComVisible( true )]

 [Guid( "5231D3C0-3465-493C-BFD8-9BEA4AAB25E1" )]

 [InterfaceType( ComInterfaceType.InterfaceIsDual )]

 public interface IAddinUtilities

 {

   void TestArchitecture( int count );

 }

 [ComVisible( true )]

 [ClassInterface( ClassInterfaceType.None )]

 public class AddInUtilities : IAddinUtilities

 {

   public void TestArchitecture( int count )

   {

     MessageBox.Show( count.ToString() );

   }

 }

}

(Automation client snippet)

var xlApp = Activator.CreateInstance( Type.GetTypeFromProgID( "Excel.Application" ) ) as Excel._Application;

object progId = "BTR.Extensibility.Excel.RBL.2003";

var comAddin = xlApp.COMAddIns.Item( ref progId );

var test = comAddin.Object as IAddinUtilities; // I've tried the actual class AddInUtilities cast as well

Any information would be greatly appreciated.

# April 20, 2008 12:11 AM

andreww said:

Terry - your code looks ok. It is an odd choice to use implicit types when you know for sure what the types must be (Excel._Application, COMAddIn and IAddinUtilities), but it should still work.

In the simplest case (like both your simple code and mine, where we try to retrieve the COMAddIns collection immediately after launching the app) there is an outside chance of a race condition where Excel has not fully initialized its COMAddIns collection, or (more likely) each add-in has not yet set its COMAddIn.Object value. In this event, you'd get a NullReferenceException not a cast exception.

So, if you're still getting a cast exception, it must be due to some mismatch in type registration. The IAddInUtilities interface should be in the registry under HKCR\Interface. Also, this should map to the typelib, which will have the GUID you used for the assembly that contains IAddInUtilities (either the separate DLL or the add-in DLL, depending on how you've implemented it).

# April 20, 2008 10:28 PM

Terry Aney said:

Thanks for the info.  The implicit types is more 'laziness' than anything else :$.

Anyway, My COMAddIn.Object is set to _ComObject (or something like that) and I didn't see my GUID under HKCR\Interface :O

As I mentioned above, I'm not marking the assembly as 'Register for COM interop' but rather manually running regasm after copying dll to proper location.  There isn't something I need to run in addition to regasm to 'register interfaces' is there?

You also asked about whether I have separate DLL or just an add-in DLL...just to answer (I stated above as well, but could be easily overlooked), I have only a single add-in dll.

Sorry for the ignorance on COM interop, I've minimal experience with it.  I appreciate the back and forth and still hoping you might have some idea and/or other users reading this might have had same problem and fixed (yn).

Anyway, thanks again.

# April 21, 2008 12:00 AM

andreww said:

I don't understand why you wouldn't use the VS 'Register for COM Interop' project setting. This not only registers your assembly for COM interop, it also exports and registers a COM typelib, which as I mentioned is cross-referenced by your interface registration. If you need to use regasm directly for some reason, then you should use the /tlb switch (or tlbexp) to get a typelib, and make sure that both are correctly registered. I'm not sure, but I suspect you'll also have to manually register your interface.

# April 21, 2008 1:41 AM

Terry Aney said:

Well, the only real reason for avoiding 'Register for COM Interop' is that I wanted to know the manual steps I needed to take so that I knew how to set it up on other people's computer.  I probably have not given the VS setup project enough credit and maybe it'll figure all this out as well.  In any case, I guess I'll try register for COM interop and see how far I get.

Thanks for reply.

# April 21, 2008 10:19 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker