Using .Net DLL in VB6

0
266

Introduction

This article has the intention to help teams interested in migrating VB6 applications to .Net (C# or VB.Net) or improve VB6 applications with .Net modern resources. The full migration of a VB6 project to .Net can be costly and complex. The approach described here allows you to convert small pieces of code at time. Another possibility is not to convert any code, but just add the new requirements directly in .Net avoiding increace the legacy application.

Background

There are several articles helping to create .Net user controls to use in VB6 applications. This approach simplifies the process with a generic COM Assembly developed in C# that uses reflection to call any another DLL that we want. With this generic DLL we just need one registration in the OS and encapsulate a lot of complex code. Not having the need to register several DLL in the OS is usefull when you have an enviroment with several machines.

Using the code

Creating a COM Assembly

In the Visual Studio create a new Class Library project. In the project Properties, click on the Application tab, click on the Assembly Information button, check the option Make assembly COM-Visible. In the Build tab, check the option Register for COM interop

Add a new class CSharpInteropService.cs to the project.

The interface below expose an event used to invoke an action in the VB6 application. It allows to open a VB6 form that hasn’t been converted yet.

[ComVisible(true), Guid(LibraryInvoke.EventsId), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ILibraryInvokeEvent
{
 [DispId(1)]
 void MessageEvent(string message);
}

The interface below expose the method used to call a DLL.

[ComVisible(true), Guid(LibraryInvoke.InterfaceId)]
public interface ILibraryInvoke
{
 [DispId(1)]
 object[] GenericInvoke(string dllFile, string className, string methodName, object[] parameters);
}

The class below is the implementation of the interfaces. GenericInvoke receives the DLL full patch, class name and method name that will be invoked using reflection. You can send an array of parameters. At the end it is going to return an array. GenericInvoke expect a methot called MessageEvent in the DLL target to create a delegate with OnMessageEvent. You need to declare MessageEvent in the target only if you want to communicate back with VB6.

[ComVisible(true), Guid(LibraryInvoke.ClassId)]
[ComSourceInterfaces("CSharpInteropService.ILibraryInvokeEvent")]
[ComClass(LibraryInvoke.ClassId, LibraryInvoke.InterfaceId, LibraryInvoke.EventsId)]
public class LibraryInvoke : ILibraryInvoke
{
 public const string ClassId = "3D853E7B-01DA-4944-8E65-5E36B501E889";
 public const string InterfaceId = "CB344AD3-88B2-47D8-86F1-20EEFAF6BAE8";
 public const string EventsId = "5E16F11C-2E1D-4B35-B190-E752B283260A";

 public delegate void MessageHandler(string message);
 public event MessageHandler MessageEvent;

 public object[] GenericInvoke(string dllFile, string className, string methodName, object[] parameters)
 {
 Assembly dll = Assembly.LoadFrom(dllFile);

 Type classType = dll.GetType(className);
 object classInstance = Activator.CreateInstance(classType);
 MethodInfo classMethod = classType.GetMethod(methodName);

 EventInfo eventMessageEvent = classType.GetEvent("MessageEvent", BindingFlags.NonPublic | BindingFlags.Static);

 if (eventMessageEvent != null)
 {
 Type typeMessageEvent = eventMessageEvent.EventHandlerType;

 MethodInfo handler = typeof(LibraryInvoke).GetMethod("OnMessageEvent", BindingFlags.NonPublic | BindingFlags.Instance);
 Delegate del = Delegate.CreateDelegate(typeMessageEvent, this, handler);

 MethodInfo addHandler = eventMessageEvent.GetAddMethod(true);
 Object[] addHandlerArgs = { del };
 addHandler.Invoke(classInstance, addHandlerArgs);
 }

 return (object[])classMethod.Invoke(classInstance, parameters);
 }

 private void OnMessageEvent(string message)
 {
 MessageEvent?.Invoke(message);
 }
 }

Registering the COM Assembly

After building the project you will get a DLL and TLB file. You need to register this assembly using the RegAsm.exe tool. This tool is located in your .Net Framework version in C:\Windows\Microsoft.NET\Framework64 or C:\Windows\Microsoft.NET\Framework.

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /codebase "C:\Temp\CSharpInterop\CSharpInteropService\CSharpInteropService\bin\Debug\CSharpInteropService.dll" /tlb:"C:\Temp\CSharpInterop\CSharpInteropService\CSharpInteropService\bin\Debug\CSharpInteropService.tlb"

To unregister use /u instead of /codebase.

Invoking a DLL

With the assembly registered you just need to select it at VB6 project reference. The code below shows how to make a basic call.

Dim param(0) As Variant
param(0) = Me.hWnd 
 
Dim load As New LibraryInvoke
Set CSharpInteropServiceEvents = load
 
load.GenericInvoke "C:\Temp\CSharpInterop\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll", "ClassLibrary1.Class1", "ShowFormParent", param

The C# DLL

[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

public void ShowFormParent(long parent)
{
 Form1 form = new Form1();
 form.Show();

 IntPtr p = new IntPtr(parent);
 SetParent(form.Handle, p);
}

private delegate void MessageHandler(string message);
private static event MessageHandler MessageEvent = delegate { };

public static void OnMessageEvent(string message)
{
 MessageEvent(message);
}

Limitations

In the current stage there is a limitation when a c# form is opened with ShowDialog(). In this case the event in VB6 is not invoked when the form is opening, but before open the form and after closing it the comunication works.

LEAVE A REPLY