A few days ago, an article by Muhammad Musa Ali on Exposing COM interfaces of a .NET class library for Late Binding, appeared on Code Project to show all you VB guys how to create a class that can be used for interop with the world outside, i.e., inside a non-.NET client such as VB, VBA, JScript, ...
Being more of a C# guy myself, I was astounded that there is such a thing as a COM class wizard for VB.NET class libraries but not for C#. Something had to be done to correct this injustice ;-), and here it is: a COM class wizard for C# class libraries.
How to expose a class for COM interop (the C# way)
Muhammad depicts two ways to expose a class for COM interop: the easy way and the hard way.
The easy way is what VS.NET does when you let it create a COM class using the wizard. Basically, three GUIDs are created: for the class itself, the COM-visible interface this class implements, and for the events the class fires. These three GUIDs are stuffed into a
ComClassAttribute and that's all about it. The compiler takes care of everything else.
Until that day, I had heard nothing about
ComClassAttribute, and so I looked up the attribute on MSDN. Very quickly, I found out why this attribute had missed me completely: it's located in the
Microsoft.VisualBasic namespace which I usually don't touch.
So, using this attribute in your C# classes might be possible if you add a reference to
VisualBasic to your projects (admittedly, I didn't try it extensively), but this additional reference just to get a single class attribute seemed a bit overpowered.
So, I tried the hard way: telling the compiler what to do by defining all the interfaces and attributes myself.
There's a pretty exhaustive article on this topic on Code Project as well (Understanding Classic COM interoperability With .NET Applications), so I just depict the important steps:
Define an interface you want to expose to COM
COM is interface based, so your class has to tell COM what methods and properties it is going to expose.
Create your class and let it implement this interface
Somebody has to do the actual work, so implement the interface and fill your methods and properties with life.
Define an interface for events you want to raise
In case you want to raise events for COM clients, these events (or rather event handlers) have to be declared in the form of an interface as well. Luckily, you won't have to dig very deep into COM's event model or handle ConnectionPoint details, the framework does this for you.
Plug everything together using class attributes
This is where the internal plumbing happens. Tell the framework that your class exposes an interface for COM by assigning GUIDs, using
ClassInterfaceAttribute on your class and
InterfaceTypeAttribute on your interface. If you want events, then assign an
InterfaceTypeAttribute to your events interface as well, and tell COM that this interface is to be used by specifying a
ComSourceInterfacesAttribute. This attribute has several constructors with string arguments, but using these is also quite error-prone since .NET is very picky on the format of these types, so whenever possible, you should use the constructor receiving a Type like in:
Another important thing to do is to add a
DispIdAttribute to each of your published methods, properties, and event handlers, or else they won't work as expected.
I'm inherently lazy, can't someone do this for me?
These steps shown above are quite error-prone if you have to do them by hand every time. VS.NET has the concept of wizards to create code, so why not use it?
To counter the VB.NET advantage of the COM class wizard, I dug a little into how these wizards work and how you could add your own wizard. Once you found the right files to change, this was quite easy.
For example, the template for my new C# COM class has a class declaration like this:
public class [!output CLASS_NAME] : [!output INTERFACE_NAME]
public class ComClass1 : IComClass1
Can I have this, too?
To try it out yourself, just use the link to the installation package I've added at the top of this article. It's an MSI package that's searching for the path of your VS.NET 2003 installation, and then installs the new wizard.
After installation, you can use a new template COM-Class in a C# class library project.
DISCLAIMER: Although I've tested the package on my computers, I cannot make any guarantees that it will work on your machine or that it will not harm your computer in any way. If you install the package and your computer blows up afterwards, I cannot be held responsible!
The installation package should work on German and English installations of VS.NET 2003. If you have a different language installed, then you might have to create copies of the template files and script in the corresponding subdirectory for your language code.
The wizard files can be found at <VS.NET 2003 Root>\VC#\VC#Wizards\CSharpComClass with subdirectories 1031 (German) or 1033 (English) in the Scripts and Templates subdirectories, resp.
If you follow the steps, then COM interop isn't really that hard. Just keep in mind that the environment you test your classes in also has its influence on the results. I spent almost a whole weekend wondering why the event fired by my test class (several seconds after one of my class' functions was called) was received in a scripting environment but (seemingly) not in a VBA client, although everything else worked. Finally, I found out that the object I wanted to receive the event had been destroyed before the event was raised :(.
And don't forget to vote if you like this article and the new wizard!
- 13.07.2004 - Initial release.
- 14.08.2004 - Update to 1.0.1.
- Changed interface type for published class interface to
ComInterfaceType.InterfaceIsDual to allow for early binding as well.
- Changed suggested
DispIds from 0 to 1.
- Minor formatting changes in class template file.