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.

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:

[ComSourceInterfaces(typeof(IComClassEvents))]

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.

Without going into too much detail, most of the wizards creating a class for you use a template file containing most of the code the final class will contain. This template then is copied and modified by a JavaScript script to insert the correct class or namespace names, for example. Additionally, project options can be modified as well (I used this to set the "Register for COM interop" flag to true in a class library receiving such a new COM class).

For example, the template for my new C# COM class has a class declaration like this:

publicclass [!output CLASS_NAME] : [!output INTERFACE_NAME]

The parts in brackets are then replaced by variable values you create as part of the JavaScript file, resulting in a valid class declaration, provided you specify the right arguments:

publicclass 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.

I've also included the class template, JavaScript file, and wizard definition files (*.vsz and *.vsdir) as "source" for this article (link at the top), so if you don't trust my installer (or in case it doesn't work for you), you can use these files to add the new template by yourself.

Conclusion

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!

Version history

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.

Happy coding,

mav

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

I'm attempting to use vb.net and base it on your C# example I've been able to get the "ShowMessage" call from javascript to the Control to work but i can't seem to get the Control to call javascript "CloseSelectd" to work... any help you can give would be greatful

Hi!I've created the COM-compatible DLL in way the author suggested. But i can't create variable for control in MFC application. Message:"The ActiveX Control is not registered properly, or its type library number is incorrect".How can I call methods in my control?Thanks

After manually installing this wizard (the installer didn't copy the .vsz file to the ..\VC#\CSharpProjects), I can see the CSharpComClass in the list of templates. But when I select it, I get the error: 'undefined' is null or not an object.

To correct the problem, I have to uncheck the reference, close VB, then re-open it, and re-import the library.

This is VERY problematic, since I am developing COM components for my clients. I Must not recompile the vb6 program whenever I send them a new version of my COM DLL.

On the other hand, VB.NET classes using the [ComClass] attribute seem to work fine. If I add a private member and recompile, vb6 will not give me any errors. The thing is- I am unable to make it work with C#.NET.

I think you didn't specify Function1 in your interface (IComClass1) correctly. You have to tell COM which methods/properties your objects supply and then implement them. Seems as if you've only done the second part.

Hi While i try to run the VB application(Standard exe)in which i dragged and dropped the .Net class library with events,I'm getting the Run time error "The specified memory location can't be read...." Can you please help me out to solve the problem.

Sorry, don't have VB6, can't help you.But you can try accessing your COM object from a VBS, for example. If you get the same error chances are very good that the problem lies in your code and not the way a .NET class is made visible for COM.

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.

At first i want 2 thank you for this very helpful piece of work. I am not very sophisticated in development of COM Components but i have to write one just these days.Its ready to use now but one Problem is still in my way and i hope someone can help me out.On the Computer i have developed the COM Comp. (C# .NET) the dll is automatically registered in COM-Interop and i can use it in VB6 without any Problem. But how can i register it on any other Computer without the IDE installed. I have tried regsvr32 und installutil but both failed.

You were really close to registering the COM object, but you have to use regasm for a .NET assembly, not regsvr32.Please note that the /codebase option is also required if the assembly has not been registered with the GAC.

muchas gracias i have found this just right in this moment .Will add a procedure for un/registering the types after Installation to the Installer right now. Thank you very much for your help Weekend can come now hehe

Sorry, can't tell you the file layout for Whidbey, I'm still waiting for my MSDN authorization to even download the beta...

But you could search for *.vsz and *.vsdir files, Whidbay might use the same structure for its wizards as VS2003 does.

In fact, what the installer does is to determine where VS.NET 2003 (7.1) stores its ProjectItem templates and wizards by searching the registry:HKLM\SOFTWARE\Microsoft\VisualStudio\7.1\Projects\{FAE04EC0-301F-11d3-BF4B-00C04F79EFBC} has an ItemTemplatesDir and a WizardsTemplatesDir value.

The .vsz file lands in <ItemTemplatesDir>\ProjectTemplates, the .vsdir file in <ItemTemplatesDir>\ProjectTemplates\LocalProjectItems.

Under <WizardTemplates> a directory for the wizard is created, holding a Scripts and a Templates subdirectory for the .cs and the .js file, resp.

With this information you could be able to figure out where to put the files for Whidbey...

Drop me a line if you succeed, will you?Then I'll update the article to describe what to do.

Hi,it looks like excelent work, I'm just started with vb 6 after years in c++. (you might ask why ;^) butI want to move to C# and I need to be able to call dll's from a script so being hopeless at vba, i was wondering if i could get you to throw me a sample whíth a call from java script on a web page. something like:

This is great! You just saved me hours if not days of porting an old COM Server (C++) into a COM-capable .NET (C#) assembly.

Two things:1. Sample code shows usage of DispId attributes to specify the COM dispatch identifier (DISPID) of a method, field, or property. One should avoid, however, creating DISPID of 0 unless he/she is designating a method, field, or property as default member of a class.

2. This wizard would be even better if you could select type of interface: dual, IDispatch only, or IUnknown only. But, I guess, one could simply create two additional templates that address these different interface types.

ad 1:Good point, I'll use 1 as starting point for DispIds in the next release of the wizard.

ad 2:I'll see what I can do. Why I chose not to use an AutoDual interface was increased compatibility with all kinds of clients.I read somewhere that VB clients had problems with dual interfaces, but couldn't try it myself. So I used the type of interface that promised the least problems.

AutoDual is used in conjunction with the ClassInterfaceAttribute, which you apply to coclasses. In your case, None is perfect - it tells the compiler that no class interface is autogenerated for the class. And you are right - AutoDual does have negative implications on versioning.

What I was talking about is InterfaceIsDual and InterfaceIsIUnknown members of ComInterfaceType enum, which works in conjuction with InterfaceTypeAttribute. You can attach this attribute to an interface only.

InterfaceIsIDispatch will work fine for the most part. However, some automation clients will perform significantly better if you allow them to bind early. Thus, I'd provide InterfaceIsDual interface so everyone is happy.

PS. Another cool thing would be an ability to "import" a type library - basically, port IDL definition into C# code. But that's just my lazy side talking.

Oh, now I know what you mean.I misread the comment, always being in a hurry...

I'll make the InterfaceTypes InterfaceIsDual as you suggest for the next version so that early binders can get increased performance.

Regards,mav

P.S.:tlbimp is just the tool to perform this import. Do you need anything more that tlbimp can't do?

-------------------Update:I've tried setting the Interface type of the COM class interface to InterfaceIsDual and couldn't find any negative impacts so far.But setting the event(!) interface to InterfaceIsDual breaks the ability to use such a class in a scripting environment (I tried vbs). VBS seems to pick the wrong interface definition (since it also happens with InterfaceIsIUnknown) and creates a null reference exception when I try to fire an event :(I'll dig into it on why this happens and keep you informed.

Tlbimp generates wrapper assembly that managed code can use to call COM components. I believe you still have to provide the original dependency whether you are importing just the type library or the COM server.

See, I am porting a C++ out-of-process COM server into .NET/C#. The requirement is to eliminate any dependencies, while providing existing COM clients with a substitute interface. Ideally, none of these COM clients will need to be rebuilt. One way of doing this is to re-define all the interfaces and coclasses within managed assembly, preserving all the ProgIDs, GUIDs, and DispIDs. I can import most of the method definitions using a combination of tlbimp and ildasm (well, the reflector by Lutz Roeder). However, the rest of the plumbing has to be done by hand. This is where your wizard came in handy - I'm almost done defining all the COM plumbing.

Re: Update.Event interfaces must be attributed with InterfaceIsIDispatch (i.e.: late bound). I'll let you know on Monday why (my COM bible is in the office if don't figure out on your own.

Btw, what's your take on using this wizard to aid development of commercial software?

Ok, for actual porting tlbimp alone isn't sufficient, but in combination with Reflector you can save a lot of manual and error-prone work.

I still couldn't find out exactly why clients choke up on IUnknown event interfaces, so if you can provide an explanation I'd be very interested.

Regarding development of commercial software: I'm not quite sure what you mean. If your question is whether I object on anyone using my wizard to create COM classes which are used in commercial software, then the answer is a strict NO! Go ahead, use it for whatever you like!I think one of the best concepts of CP is that - usually - anything published here can be used or included in your own programs to your likings. People trying to restrict the way their code is used by others (sometimes even AFTER the initial article has been up for a while) shouldn't publish articles on CP in the first place. A developer's life is hard enough without having to remember which articles influenced his code and to check all the time whether he's still permitted to use the code! If you want to share knowledge, then fine, but don't expect financial benefit from doing so.

I'm happy if I can give something back to the community after getting so many inspirations and so much knowledge from it.

The Wizard creates a ProgId for you (but you can easily change this to your heart's desire) and you can use this ProgId for CreateObject.

As an example I've created a COM Class using the wizard, uncommented the event, interface and function declarations and added an additional function void FireEvent1(int a, int b) that just fires Event1 with the parameters given.The FireEvent1() declaration has to be added to the interface declaration with a new DispId as well, of course. Otherwise you won't be able to call this function via COM.I've set the ProgId for the class to "EventSource.ComClass1".Save and build, registration of the new class is done automatically.

For events to work in your VBScript you have to use CreateObject with 2 parameters like shown in this vbs snipplet: