Using .NET Class Libraries from VB6 Clients


Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame


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;

      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!

This article was originally published on March 24th, 2004

About the Author

Andreas Wieberneit

I started my IT career during the late 80s, using programming environments such as C, Pascal and dBase. With the advent of the 90s, I discovered C++ and OOP. Since then, I devoted most of my work to build modular systems, composed of DLLs, COM objects, and service applications; to participate in the development of multi-tier business systems; and to communicate with Oracle, SQL-Server and postgreSQL databases, all kinds of hardware devices, as well as smart cards. With the beginning of the new millenium I switched to .NET, where the need to integrate existing components forced me to deal with the secrets of interoperability. During the past years, I shared my working hours between managing software projects and doing hands-on implementation of software systems. Currently, I live and work in Toronto, Canada. You can reach me at awieberneit@yahoo.ca.


Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date