A Better Way To Call A .NET Assembly From COM
In my previous post Call a C# Assembly From VB6 I mentioned that there was a better way to pull off the interop required for using a .NET assembly in VB6. You can think of the technique used in that article as the “quick and dirty” approach. It works, but leans heavily on .NET’s ability to automatically specify the settings in the generated COM interface. That’s ok, but there is a cost: you have to recompile your COM clients every time you recompile your .NET component, and you cannot use late binding which, for example, is required for use by scripting clients. For just a little extra effort, you can exercise a greater level of control, and not have to pay these costs.
The technical background for what follows is covered very nicely in many places. I’m just going to provide a “how to” in what follows. For background on why it is done this way, you can check out COM Interop Exposed, or another similar resource on the web.
I’ll reengineer the Reverser class from the Call a C# Assembly From VB6 article to use this better approach.
Create an Interface
Create an interface for the methods you want to expose in COM. The interface for the Reverser class would be defined like so:
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4: using System.Runtime.InteropServices;
5:
6: namespace String.Utilities {
7: [Guid("F60865B6-D2D7-4435-8B0B-D2A09D88548C")]
8: [ComVisible(true)]
9: public interface IReverser {
10:
11: string Reverse(string str);
12:
13: string BetterReverse(string str);
14: }
15: }
Notice that we have added a GUID. We have done this to control versioning. In the prior article we noted that you should recompile your COM clients every time the .NET component is changed. By specifying a GUID for our interface and classes, we will be able to change the implementation of our .NET component without requiring a recompile of our COM clients. See COM Interop Exposed for more details.
Implement the Interface
Our new Reverser class will implement the new IReverser interface:
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4: using System.Runtime.InteropServices;
5:
6: namespace String.Utilities {
7:
8: [Guid("80705AFF-708F-46f9-BA50-D69A96753A57")]
9: [ComVisible(true)]
10: [ClassInterface(ClassInterfaceType.None)]
11: public class Reverser : IReverser {
12:
13: // Types must have a public default constructor
14: // to be instantiated through COM. Managed public
15: // types are visible to COM, but without a public
16: // default constructor (a constructor without
17: // arguments), COM clients cannot create an
18: // instance of the type.
19: public Reverser() {
20: }
21:
22: #region IReverser Members
23:
24: public string Reverse(string str) {
25: // This method is from Mladen Prajdić's
26: // I want some Moore blog:
27: // http://weblogs.sqlteam.com/mladenp/
28: // archive/2006/03/19/9350.aspx
29:
30: char[] charArray = str.ToCharArray();
31: int len = str.Length - 1;
32:
33: for (int i = 0; i < len; i++, len--) {
34: charArray[i] ^= charArray[len];
35: charArray[len] ^= charArray[i];
36: charArray[i] ^= charArray[len];
37: }
38: return new string(charArray);
39: }
40:
41: public string BetterReverse(string str) {
42: // This method is from Mladen Prajdić's
43: // I want some Moore blog:
44: // http://weblogs.sqlteam.com/mladenp/
45: // archive/2006/03/19/9350.aspx
46:
47: char[] charArray = new char[x.Length];
48: int len = x.Length - 1;
49: for (int i = 0; i <= len; i++)
50: charArray[i] = x[len - i];
51: return new string(charArray);
52: }
53:
54: #endregion
55: }
56: }
Notice that this class also defines a GUID. As noted for the IReverser interface, this is also done for the actual class to control versioning. Also, the ClassInterfaceAttribute specifies that no automatic interface is to be generated. In this case, the first interface implemented by the class will be the default interface in COM. This allows us to support late binding as well as early binding in COM clients (previously, only early binding was supported).
Notes
You will still have to set the project to register for COM interop. Note however that there is no need to change the ComVisiible setting in the AssemblyInfo.cs file:
1: [assembly: ComVisible(false)]
In fact, by leaving it false, we can control exactly which interface methods are visible by overriding this setting with the [ComVisible(true)] attribute on an interface by interface, or method by method basis.