Passing Objects to Exposed Add-in Methods

I’ve posted a few times on the best way to expose methods from an add-in to automation clients – for example, here, here and here. So far, in my examples, I’ve described very simple exposed methods that take no parameters – but what happens if you want to expose a method that does take one or more parameters? The answer depends on the type of the parameter(s).

If you want to pass some simple type – a value type or a string – there’s nothing extra you need to do. So, let’s suppose your interface and exposed object look like this:

[ComVisible(true)]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

public interface IAddinUtilities

{

    void DoSomething(string s);

}

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities :

    StandardOleMarshalObject,

    IAddinUtilities

{

    public void DoSomething(String caption)

    {

        Globals.ThisAddIn.CreateNewTaskPane(caption);

    }

}

…and your add-in class looks implements a custom CreateNewTaskPane method like this:

public partial class ThisAddIn

{

    …

    internal void CreateNewTaskPane(string caption)

    {

        Microsoft.Office.Tools.CustomTaskPane taskPane =

            this.CustomTaskPanes.Add(new UserControl(), caption);

        taskPane.Visible = true;

    }

}

…then, your automation client can call into the exposed object like this:

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

excel.Visible = true;

object addinName = "ComServiceOleMarshal";

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

ComServiceOleMarshal.IAddinUtilities utils = null;

while (utils == null)

{

    utils = (ComServiceOleMarshal.IAddinUtilities)addin.Object;

    System.Threading.Thread.Sleep(100);

}

utils.DoSomething("Hello");

So far, so good. But, suppose that instead of a simple value type or string, you want to pass some reference type object to the exposed method. Let’s say you have some class like this:

public class SomeObject

{

    public int Number;

    public string Name;

    public SomeObject(int n, string s)

    {

        Number = n;

        Name = s;

    }

}

…and your interface defines a method that takes an object of this class as a parameter, like this:

[ComVisible(true)]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

public interface IAddinUtilities

{

    //void DoSomething(string s);

    void DoSomething(SomeObject o);

}

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

public class AddinUtilities :

    StandardOleMarshalObject,

    IAddinUtilities

{

    //public void DoSomething(String caption)

    public void DoSomething(SomeObject o)

    {

        string caption = String.Format("{0}={1}", o.Number, o.Name);

        Globals.ThisAddIn.CreateNewTaskPane(caption);

    }

}

…and you want to invoke this in your automation client like this:

ComServiceOleMarshal.SomeObject o =

    new ComServiceOleMarshal.SomeObject(123, "Hello");

//utils.DoSomething("Hello");

utils.DoSomething(o);

If you try this, you’ll get an exception like this:

System.InvalidCastException: Unable to cast object of type 'System.__ComObject' to type 'ComServiceOleMarshal.SomeObject' at ComServiceOleMarshal.IAddinUtilities.DoSomething(SomeObject o)

What’s happening is that the runtime is attempting to marshal the custom object across the 2 processes, and failing because the object is not marshalable. The simplest solution to this is to mark the custom class as Serializable, then it will be marshaled by simply serializing it (and deserializing it) via a memory stream:

[Serializable()]

public class SomeObject

{

 

ComServiceOleMarshal_WpfClient_SerializableObjectParam.zip