Introduction
It often happens that someone needs to use a .NET class library from within a Visual Basic 6.0 project. Fortunately, .NET has some interoperability features that allow us to expose our classes to clients as if they were COM objects. And, COM objects can be used by VB6 applications. So, what do you have to do if you want to expose a complete COM interface from your class library and use it from within your VB6 app?
Of course, there is some documentation within the MSDN. Look at the “Interoperating with Unmanaged Code” section, and you will find some very useful hints. There is also an article here on CodeGuru, contributed by Steve Green, showing how to expose properties and methods: “Consuming .NET Components from COM-Aware Clients.”
From these sources, the bits and pieces become clearer. But what do you do if you want to expose events as well as properties and methods, all from the same class library? And how can your VB6 client use them? Well, it took some effort to find out.
Implementing the .NET Class Library
Basically, if you want to expose your classes to a COM client, you have to expose an interface. If you want to expose not only methods and properties, but also events, you have to expose even two interfaces: one for the methods and properties, and the other one for the events. I will provide a very simple class library, exposing just one method, one property, and one event, to demonstrate how these interfaces look like. Essentially, this class library consists of two interfaces and one class. Let’s start with the interface that exposes the property and the method:
using System;namespace ComInterop
{
public interface IComInterop
{
void SomeMethod( int nValue );
int SomeProperty{ get; set; }
}
}
Pretty straightforward. Let’s look at the second interface; it exposes our event:
using System;
using System.Runtime.InteropServices;namespace ComInterop
{
[InterfaceTypeAttribute( ComInterfaceType.InterfaceIsIDispatch )]
public interface IComInteropEvents
{
void SomeEvent();
}
}
The only difference here is that we need to expose this interface as an IDispatch interface. By default, interfaces are exposed as dual interfaces, which means IUnknown as well as IDispatch. Our event interface, however, must be exposed explicitly as an IDispatch interface. We use the InterfaceTypeAttribute to do this. Otherwise, the event interface is similar to the previous interface.
Now that we have defined our two interfaces, we can do the implementation, which in .NET is a class:
using System; using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyKeyFile( "ComInterop.snk" )] namespace ComInterop { public delegate void SomeEventHandler(); [ComSourceInterfacesAttribute( "ComInterop.IComInteropEvents" )] public class CComInterop : IComInterop { private int m_nSomeProperty = 0; public event SomeEventHandler SomeEvent; public void SomeMethod( int nValue ) { m_nSomeProperty = nValue; SomeEvent(); } public int SomeProperty { get{ return m_nSomeProperty; } set{ m_nSomeProperty = value; } } } }
Defining a delegate to raise an event is straightforward, so I will not comment on that. Our event will be raised by the only method included in the sample code, allowing the VB6 client to catch it every time the method is called.
The interesting point here is that our implementation class has to inherit from both interfaces defined before, but in a very different way. While inheritance from IComInterop is defined .NET style, inheritance from IComInteropEvents is defined with the help of another attribute: the ComSourceInterfaceAttribute, which defines those event interfaces that will be exposed as COM connection points.
In our sample, we want to expose the IComInteropEvents interface of the ComInterop namespace as a connection point interface, so we define this interface as our event source interface.
When imitating a COM interface, we need to prepare our class library to be registered. Hence we have to provide a strong name, which in turn requires a key file, and that key file will be created by including the attribute AssemblyKeyFile.
To make things easy, a simple batch file is provided to build the library, as well as registering it. Make sure your paths are correctly set, either by running vcvars32.bat or by adding them manually.
Implementing the VB6 Client
To make calls into our .NET class library and to catch its event, a very simple VB6 application is provided. It consists of one form, containing just one button.
The only difference between this code and a VB6 source that references an actual COM object is this:
Public WithEvents m_oComInterop As ComInterop.CComInterop Public m_iComInterop As ComInterop.IComInterop
We have to define two object references instead of one. Of course, both references have to be assigned to an instance of our class library, which we do when the form is loaded:
Private Sub Form_Load() Set m_oComInterop = New ComInterop.CComInterop Set m_iComInterop = m_oComInterop m_nCounter = 0 End Sub
We simply create one instance, and then assign the resulting reference to the other reference as well. The rest of the code is self-explanatory:
Public m_nCounter As Integer Private Sub cmdCallMethod_Click() m_nCounter = m_nCounter + 1 m_iComInterop.SomeMethod m_nCounter End Sub Private Sub m_oComInterop_SomeEvent() MsgBox "Counter = " + CStr(m_iComInterop.SomeProperty) End Sub
Every time the button “cmdCallMethod” is clicked, the counter will be incremented, and the class library method will be called. This, in turn, raises the class library event, which will be caught by the VB6 app, thereby launching a message box, displaying the current value of the counter, which is being retrieved by calling the class library’s property.
When building the VB6 app, don’t forget to add a reference to the ComInterop class library!