Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using .NET DLL in VB6

0.00/5 (No votes)
8 Nov 2017 1  
A generic DLL that call any .NET DLL in VB6.

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 increase 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 useful when you have an environment with several machines.

Using the Code

Creating a COM Assembly

In 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 exposes 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 exposes 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 expects a method 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 (32 bits). This tool is located in your .NET Framework version in C:\Windows\Microsoft.NET\Framework.

C:\Windows\Microsoft.NET\Framework\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.

If you try registering CSharpInteropService.dll from a network location and receive an error, it may be necessary to include the following lines in the regasm.exe.config inside the configuration key:

<runtime>
    <loadFromRemoteSources enabled="true"/>
</runtime>

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 'To made the c# form a child of vb6 mdi
   
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);
}

Network Environment

If you try to load an assembly from a network location and receive an exception (System.IO.FileLoadException), try to create a config file for the VB6 executable. In the case of my sample project, I create a Project1.exe.config for the Project1.exe with the following content:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <runtime>
      <loadFromRemoteSources enabled="true"/>
   </runtime>
</configuration>

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 open, but before opening the form and after closing it, the communication works.

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.

A list of licenses authors might use can be found here