Archive for the ‘.net framework’ tag
Getting 2 C# Outlook addins to talk together
The Outlook Object Model (OOM) exposes the COMAddins collections of COMAddin object which can be used by Outlook plugins to communicate together. The communication needs to be done through a COM interface. C# and the .NET framework makes it very easy.
You first need to make a ComVisible interface which the caller will use to communicate with the callee. The sample we will work with is a simple class that will call System.Windows.Forms.MessageBox.
using System;
using System.Runtime.InteropServices;
namespace MessageBox
{
[ComVisible(true)]
public interface IMessageBox
{
void MessageBox(String msg);
}
}
Next you need to make the callee addin. This addin can be made using VTSO or without it. Addin Express would work too. The callee of course needs to implement IMessageBox interface.
[ComVisible(true)]
[ComDefaultInterface(typeof(IMessageBox))]
public partial class ThisAddIn : IMessageBox
{
public void MessageBox(String msg)
{
System.Windows.Forms.MessageBox.Show("MessageBox() call: " + msg);
}
protected override object RequestComAddInAutomationService()
{
return this;
}
/* ... snip ... */
}
This is not the full code of the addin. I have removed the boring part generated by VTSO.
There are 2 important things to notice in the above code. The first is the ComDefaultInterface attribute, which defines which default interface is exposed to COM by the addin. This is important because the ThisAddin class derives from a non-COM-visible class. The page on the NonCOMVisibleBaseClass Managed Debugging Assistant (MDA) has the information on why this is important.
The next important thing is the implementation of the RequestComAddinAutomationService, which return an instance of the COM class to the caller. This is part of the communication protocol between addins. You can make this method return any instance of a COM visible objects. We’ve used the addin class itself to keep things simple.
Finally, the caller code amounts to accessing the COMAddins collection and finding the right object inside it to get the interface.
object msgBoxID = "MessageBox.Addin";
Office.COMAddIns addins = null;
Office.COMAddIn msgBox = null;
IMessageBox imsgBox = null;
try
{
addins = Application.COMAddIns;
msgBox = addins.Item(ref msgBoxID);
imsgBox = (IMessageBox)msgBox.Object;
// Actually use the other addin interface.
imsgBox.MessageBox("Hello, this is a selection change.");
}
finally
{
if (addins != null) Marshal.ReleaseComObject(addins);
if (msgBox != null) Marshal.ReleaseComObject(msgBox);
}
This code is pretty straightforward. The 2 important things to notice are the ref object parameter to the COMAddin.Item method and the fact that you need to use the ProgID of the callee addin when searching inside the COMAddins collection. I put emphasis on this because I lost some time trying to find the ProgID of the IMessageBox interface. The ProgID of the addin, when using VTSO, is usually the name of the assembly.
This research was done in the context of the development of the Echotracker although it is still way too early to say what feature will be built on top of that.
C# array marshalling with P/Invoke
This took me a while to figure out and nobody on the Internet seems to have figured out exactly this particular task.
Working on the Echotracker, I wanted to be able to manipulate an array of string from the JScript runtime inside an embedded web browser. Microsoft script runtimes are able to easily manipulate ActiveX objects, and it is easy in C# to make a object visible to COM so interoperability is generally not a problem.
This interoperability wasn’t so intuitive when the time came to deal with arrays. The offending C# declaration was the following.
string[] GetPhoneNumbers();
This is converted by C# into a SAFEARRAY containing BSTR objects. SAFEARRAY is the native name of VB-style multidimensional arrays and BSTR is, in general, the type used by COM to exchange strings. The declaration is nothing exotic as far as COM is concerned. With the MarshalAs attribute added, the declaration now looks like this.
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)] string[] GetPhoneNumbers();
This doesn’t work well with JScript. The first I found thing is that JScript array aren’t compatible with VB array. Microsoft provides a VBArray class for that purpose. It is supposed to deal with SAFEARRAY. Using this class sadly adds some semantic complexity to using arrays returned by ActiveX objects because they can only be used through methods such as getItem(), ubound(), lbound() instead of the normal way for JScript arrays.
Secondly, JScript can’t deal with SAFEARRAY(BSTR) object. Using the GetPhoneNumbers() method crash with the error message “VBArray expected”. The problem is that while JScript can deal with SAFEARRAY, it can’t deal with SAFEARRAY of type other than VARIANT. VARIANT is the catch-all, magic datatype introduced by Microsoft for its dynamic, untyped languages like VB and JScript. This is how I came to test the next declaration.
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VARIANT)] string[] GetPhoneNumbers();
This is also not enough for the array to be accepted by JScript, giving out the same “VBArray expected” error message. The problem is in the way the array is marshaled by C#. This page on MSDN clued me to the solution. The .NET marshaller is converting System.String objects automatically into BSTR objects. In this case, the object type needed is a VARIANT object of subtype VT_BSTR. The declaration alone isn’t enough to make the marshaller convert the string to the proper variant type.
[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VARIANT)] object[] GetPhoneNumbers();
This last declaration makes the .NET marshaller properly convert the strings into VT_BSTR variant object and the array becomes usable by the JScript runtime. In fact, once you know how the .NET runtime converts System.Object arrays, you can do without MarshalAs declaration.
object[] GetPhoneNumbers();
This is, by default, converted into a method returning a SAFEARRAY(VARIANT) object.
The lesson is: leave the .NET COM interop marshaller alone, it probably knows more about COM than you do.
