65.9K
CodeProject 正在变化。 阅读更多。
Home

在 VB6 中使用 .NET DLL

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (22投票s)

2017年7月9日

CPOL

3分钟阅读

viewsIcon

79700

downloadIcon

3153

一个在 VB6 中调用任何 .NET DLL 的通用 DLL。

引言

本文旨在帮助有兴趣将 VB6 应用程序迁移到 .NET(C# 或 VB.NET)或使用 .NET 现代资源改进 VB6 应用程序的团队。 将 VB6 项目完全迁移到 .NET 可能既昂贵又复杂。 这里描述的方法允许您一次转换一小部分代码。 另一种可能性是不转换任何代码,而只是直接在 .NET 中添加新的需求,从而避免增加旧版应用程序。

背景

有几篇文章帮助创建 .NET 用户控件以在 VB6 应用程序中使用。 这种方法使用 C# 中开发的通用 COM 程序集简化了该过程,该程序集使用反射来调用我们想要的任何其他 DLL。 使用这个通用 DLL,我们只需要在操作系统中注册一次并封装大量复杂代码。 当您有一个包含多台机器的环境时,不需要在操作系统中注册多个 DLL 很有用。

Using the Code

创建 COM 程序集

在 Visual Studio 中,创建一个新的类库项目。 在项目属性中,单击应用程序选项卡,单击程序集信息按钮,选中选项使程序集 COM 可见。 在构建选项卡中,选中选项注册 COM 互操作

向项目中添加一个新类 CSharpInteropService.cs

下面的接口公开了一个事件,用于在 VB6 应用程序中调用一个动作。 它允许打开一个尚未转换的 VB6 窗体。

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

下面的接口公开了用于调用 DLL 的方法。

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

下面的类是接口的实现。GenericInvoke 接收 DLL 的完整路径、类名和方法名,这些将使用反射调用。您可以发送一个参数数组。最后,它将返回一个数组。GenericInvoke 期望目标 DLL 中有一个名为 MessageEvent 的方法,以使用 OnMessageEvent 创建一个委托。 只有当您想与 VB6 建立通信时,才需要在目标中声明 MessageEvent

[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);
    }
 }

注册 COM 程序集

构建项目后,您将获得一个 DLL 和 TLB 文件。 您需要使用 RegAsm.exe 工具(32 位)注册此程序集。 此工具位于您的 .NET Framework 版本中的 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"

要取消注册,请使用 /u 替代 /codebase

如果您尝试从网络位置注册 CSharpInteropService.dll 并收到错误,则可能需要在 regasm.exe.config 文件中的 configuration 键内包含以下几行

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

调用 DLL

注册程序集后,您只需要在 VB6 项目引用中选择它即可。下面的代码演示了如何进行基本调用。

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

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);
}

网络环境

如果您尝试从网络位置加载程序集并收到异常 (System.IO.FileLoadException),请尝试为 VB6 可执行文件创建一个 config 文件。 在我的示例项目中,我为 Project1.exe 创建了一个 Project1.exe.config,其内容如下

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

限制

在当前阶段,当使用 ShowDialog() 打开 C# 窗体时存在一个限制。 在这种情况下,在打开窗体之前和关闭窗体之后,VB6 中的事件不会被调用,但通信可以工作。

© . All rights reserved.