Introduction
Well, maybe the title of this article is slightly exaggerated, but this is the story about how I – despite strong reluctance to do so – successfully managed to expose a .NET library of mine to COM. Furthermore, during this process, my original .NET library became even better and I actually ended up kind of liking the additional COM API.
Obviously, as most other .NET developers, I have had to deal with the tremendous amount of unmanaged code out there. Even if I would rather avoid it, from time to time, I have had to use the .NET interoperability services to consume some ActiveX component or other type of unmanaged legacy code. And I have learned to live with it. But why would someone ever think of exposing a nice and clean .NET library for a development platform (COM) that was deprecated decades ago?
Anyway, recently I found myself in a situation where I was left no choice. I had made this terrific .NET library with loads of nice functionality when the client required that the same functionality was made available through COM.
Background
There are lots of explanations out there on the Internet on how to expose .NET assemblies to COM - for example this article on CodeProject. The reason why I bother to write this article anyway is that none of the resources that I have found on the Internet describes the exact approach that I eventually chose for my project. Also, I did not find any resources giving an overview of all the small challenges I met in the process. And as we all know, the devil is in the detail.
The Example Code
The example code for this article is based on the legendary “Hello World!” example. Compared to the normally very simple structure of such examples, my code might seem unnecessarily complicated to overcome the simple task of displaying the famous text message, but still it is relatively simple and elegantly illustrates all of the challenges that I met when working with the real code base.
The central class in the example library is a Greeter
class that has a dependency to an IMessageWriter
instance that is injected into the Greeter
class through the constructor (yes, this is dependency injection in action):
public class Greeter
{
...
private IMessageWriter messageWriter;
public Greeter(IMessageWriter messageWriter) : this()
{
this.messageWriter = messageWriter;
}
...
public void Greet(GreetingType greetingType)
{
this.messageWriter.Write(greetingType.Greeting);
...
}
}
The IMessageWriter
instance is used in the Greet
method to write the message. The GreetingType
decides the exact phrasing of the greeting (much more about this later). The IMessageWriter
interface contains a single Write
method:
public interface IMessageWriter
{
void Write(string message);
}
The example library comes with a single concrete implementation of the IMessageWriter
interface – a ConsoleMessageWriter
that writes a text message to the console:
public class ConsoleMessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.Write(message);
}
}
From a console application, the following code creates a silly greeting:
internal class Program
{
private static void Main()
{
var greeter = new Greeter(new ConsoleMessageWriter());
greeter.Greet(GreetingType.Silly);
}
}
The Overall Approach
Now, let's dig into the matter. Probably because of my initial reluctance to deal with the COM interoperability at all, I decided to make a clear rule for myself – a self-imposed dogma so to speak. I would under no circumstances “pollute” my original .NET library with any COM-related stuff such as COM-specific interfaces or any of the ComVisible
-, Guid
- or ClassInterface
attributes. I would allow no references to the System.Runtime.InteropServices
namespace whatsoever. Also, I would not accept major degradations of my original library. So I ended up with a project structure like this:
All the COM-specific stuff is encapsulated in the ClassLibrary.Interop
assembly while my original ClassLibrary
assembly remains a clean .NET library.
In the ClassLibrary.Interop
assembly, I explicitly define all the COM interfaces and decorate them with the Guid
attribute:
namespace ClassLibrary.Interop
{
using System;
using System.Runtime.InteropServices;
[Guid("2c0fe71f-fb44-4fae-ab8e-779053f86737")]
public interface IGreeter
{
...
}
}
Furthermore, I create new classes inheriting from the original ones and implementing the corresponding explicitly defined COM interface. I decorate the classes with the Guid
attribute and the ClassInterface
attribute with the ClassInterfaceType.None
parameter. The ClassInterfaceType.None
parameter prevents the class interface from being automatically generated when the class metadata is exported to a COM type library. So in the below example, only the members of the IGreeter
interface will be exposed:
namespace ClassLibrary.Interop
{
using System;
using System.Runtime.InteropServices;
[Guid("8ea7f6d2-d86f-4070-aaee-b43bbed3706e")]
[ClassInterface(ClassInterfaceType.None)]
public class Greeter : ClassLibrary.Greeter, IGreeter
{
...
}
}
I don’t bother decorating the individual classes with the ComVisible
attribute because the whole point is that in the ClassLibrary.Interop
assembly I only deal with .NET types that I want to expose for COM, so instead I declare this once and for all in the AssemblyInfo
file:
[assembly: ComVisible(true)]
Dealing with the Challenges
As mentioned earlier, I met a few challenges on the way – mostly because of .NET/C# features that are not supported in COM. In the following, I will describe the individual challenges and the solutions to them.
Constructors with Parameters
COM does not support constructors with parameters. COM requires default (parameterless) constructors.
As shown earlier, the Greeter
class uses dependency injection and requires an instance of an IMessageWriter
interface provided through its constructor:
public class Greeter
{
...
private IMessageWriter messageWriter;
public Greeter(IMessageWriter messageWriter) : this()
{
this.messageWriter = messageWriter;
}
...
}
So what I did was that I created an additional protected
parameterless default constructor and a protected MessageWriter
property. The fact that these two additional members are protected
is an important point because then I can use them from my Greeter
extension class in the ClassLibrary.Interop
assembly to provide COM interoperability while still hiding these members from “normal” use of the Greeter
class within the .NET Framework – thus forcing the consumer to use the public constructor:
public class Greeter
{
private readonly List<string> greetings;
private IMessageWriter messageWriter;
public Greeter(IMessageWriter messageWriter) : this()
{
this.messageWriter = messageWriter;
}
protected Greeter()
{
this.greetings = new List<string>();
}
...
protected IMessageWriter MessageWriter
{
get { return this.messageWriter; }
set { this.messageWriter = value; }
}
...
}
Then I can introduce an Initialize
method in the COM interface of the Greeter
class and use this method to set the MessageWriter
property.
[Guid("8ea7f6d2-d86f-4070-aaee-b43bbed3706e")]
[ClassInterface(ClassInterfaceType.None)]
public class Greeter : ClassLibrary.Greeter, IGreeter
{
...
public void Initialize(IMessageWriter messageWriter)
{
this.MessageWriter = messageWriter;
}
}
So now, from a COM consumer I will have to first create the Greeter
object using the default constructor and then call the Initialize
method.
Overloaded Methods
Overloaded methods are not supported in COM. In the Greeter
class, I do have two Greet
methods with different signatures – one always making a neutral greeting and one where I can provide a specific greeting type as a parameter:
public class Greeter
{
...
public void Greet()
{
this.Greet(GreetingType.Neutral);
}
public void Greet(GreetingType greetingType)
{
this.messageWriter.Write(greetingType.Greeting);
this.greetings.Add(greetingType.Greeting);
}
}
The only way to deal with this problem is to introduce different names in the COM interface:
[Guid("8ea7f6d2-d86f-4070-aaee-b43bbed3706e")]
[ClassInterface(ClassInterfaceType.None)
public class Greeter : ClassLibrary.Greeter, IGreeter
{
...
public void Greet(GreetingType greetingType)
{
base.Greet(greetingType);
}
public void GreetNeutral()
{
base.Greet(GreetingType.Neutral);
}
...
}
Generics
.NET generics is gibberish for COM. So if you have made any generic classes or methods or if you use any of the built-in generic types, then you have to be a bit creative. In the Greeter
class, I am using the generic ReadOnlyCollection<>
to keep the greeting history:
public class Greeter
{
...
public ReadOnlyCollection<string> Greetings
{
get { return new ReadOnlyCollection<string>(this.greetings); }
}
...
}
The solution to this problem is pretty straight forward. Simply let the Greeter
extension in the ClassLibrary.Interop
assembly return an arrays of strings instead:
[Guid("8ea7f6d2-d86f-4070-aaee-b43bbed3706e")]
[ClassInterface(ClassInterfaceType.None)
public class Greeter : ClassLibrary.Greeter, IGreeter
{
public new string[] Greetings
{
get { return base.Greetings.ToArray(); }
}
...
}
Inheritance
One challenge that I met is in a different category than the others. This challenge was not due to the missing COM support for certain .NET/C# features. Rather, it was due to my self-imposed dogma about keeping my original .NET library free of COM-related stuff. As I wanted to extend the original .NET types with COM interoperability using inheritance, only inheritable types could be extended. .NET types like struct
and enum
are not inheritable.
So I had to change a couple of struct
s to classes in my original library, which didn’t really bother me too much.
The enums, however, were a bit trickier. What I did was to introduce my own Enumeration
class instead of using enum
s. This was one of the changes that I actually consider a major improvement to my original code. I have always found it annoying that enum
s could not be extended with for example a display name (for example including spaces). By introducing an Enumeration
class, exactly this can be done:
public abstract class Enumeration : IComparable<Enumeration>
{
private readonly string displayName;
private readonly int value;
protected Enumeration(int value, string displayName)
{
this.value = value;
this.displayName = displayName;
}
public string DisplayName
{
get { return this.displayName; }
}
public int Value
{
get { return this.value; }
}
...
}
The whole discussion about using enumeration classes instead of enum
s is worth a whole article by itself, but another advantage worth mentioning is that this approach can reduce the number of switch
statements that inevitably follows from the usage of enum
s. Look how elegantly the greeting text, in the form of the Greeting
property, has become a detail of a greeting type:
public abstract class GreetingType : Enumeration
{
...
protected GreetingType(int value, string displayName)
: base(value, displayName)
{
}
public abstract string Greeting { get; }
}
Now the individual greeting types can be defined, e.g. a neutral greeting type:
public class GreetingTypeNeutral : GreetingType
{
public GreetingTypeNeutral()
: base(0, "Neutral")
{
}
public override string Greeting
{
get { return "Hello World!"; }
}
}
Or a silly greeting type:
public class GreetingTypeSilly : GreetingType
{
public GreetingTypeSilly()
: base(1, "Silly")
{
}
public override string Greeting
{
get { return "Howdy World!"; }
}
}
Static Methods
The GreetingType
enumeration class brings us to the last of the challenges. In the GreetingType
enumeration class, I define 3 static
methods – one for each of the greeting types.
public abstract class GreetingType : Enumeration
{
public static readonly GreetingType Casual = new GreetingTypeCasual();
public static readonly GreetingType Neutral = new GreetingTypeNeutral();
public static readonly GreetingType Silly = new GreetingTypeSilly();
...
}
But unfortunately static
methods are not supported in COM. So, for the COM interface, I have to expose the 3 greeting type classes instead – here illustrated by the GreetingTypeCasual
class:
[Guid("b60afdd6-7f93-48eb-baf1-15196c8f779b")]
[ClassInterface(ClassInterfaceType.None)]
public class GreetingTypeCasual : ClassLibrary.GreetingTypeCasual, IGreetingType
{
}
This is why I had to make the original greeting types public
. If I wasn't going to expose my assembly to COM, I would have made the GreetingTypeNeutral
(and the other greeting types) internal
- or even private
classes within the GreetingType
class.
COM Registration
When all challenges are overcome and the ClassLibrary.Interop
assembly is ready, it must be properly registered.
In my ClassLibrary.Interop
project, I have checked the “Register for Com interop” option under the projects Build properties. This will do the trick on your own machine.
If you want to deploy the COM version of the library to other machines, you have to use the assembly registration tool RegAsm
. If you call it from a Windows batch file placed in the same folder as the assembly itself, you can for example use the following syntax:
c:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /tlb /codebase %~dp0ClassLibrary.Interop.dll
This approach requires that the assembly is signed with a strong name (even if not put in the GAC).
My guess is that most COM consumers run in 32 bit. If you want to register for 64 bit consumers, you should call the 64 bit version of RegAsm
found in c:\Windows\Microsoft.NET\Framework64.
VBA Sample
And finally, here is some sample code using the COM API from a Visual Basic for Applications (VBA) macro:
Public Sub testGreeter()
Dim ConsoleMessageWriter As New ConsoleMessageWriter
Dim Greeter As New Greeter
Dim Greetings() As String
Greeter.Initialize ConsoleMessageWriter
Greeter.GreetNeutral
Greetings = Greeter.Greetings
MsgBox Greetings(0), vbOKOnly, "Neutral greeting"
Greeter.Greet New GreetingTypeCasual
Greetings = Greeter.Greetings
MsgBox Greetings(1), vbOKOnly, "Casual greeting"
End Sub
Summary
This article describes an approach for exposing a .NET assembly to COM by handling all the COM-specifics in a dedicated ClassLibrary.Interop
assembly without having to compromise the original ClassLibrary
assembly.
Exposing .NET assembly functionality to COM does not necessarily need to be a hassle. Yes, there are indeed some challenges to overcome and, for sure, my personal preference will always be to use the .NET assembly directly. However, I do see some advantages in providing a dedicated COM API acting as a sort of “higher level” scripting API for other than hardcore .NET programmers. I kind of like the way that the explicitly defined COM interfaces in the ClassLibrary.Interop
assembly acts as a facade to the full functionality, and how for example abstract base classes and interfaces are hidden to the COM API user.