VB6与C#之间的交互,据我所知,有以下几种方式:
1.socket通讯方式,VB6有Winsocket控件,C#有专门的位于System.Net.Sockets下的通讯类
2.共享内存,共享内存C#有专门的封装类,详情见我的这篇博客
,但是VB操作共享内存麻烦,要调用Windows API(Kernel32.dll中的函数)
3.读写文件/数据库的方式。这种方式无非是轮询,一个程序写请求,另一个程序轮询读取请求然后处理请求的方式,效率较低,且开销较大
4.COM组件实现直接调用:将要调用的C#DLL写成COM组件,然后注册,然后在VB6中引用注册生成的tlb文件,但是有时候别人给我们调用的DLL不一定是COM组件的方式,且注册麻烦
5.COM组件间接调用,意思是写一个万能的C#的DLL,注册一次这个万能的DLL,这个万能C#的DLL可以调用任何其他C#程序集的任何公开的静态、成员方法.实现VB6间接调用任何其他的C#的DLL而不用注册这些C#的DLL
本文想着重介绍最后一种方式。
最后一种方式的调用流程如下:
本篇文章资源总结:
VB6 DLL/OCX,CSInterface一键注册工具
CSInterface源码
VB6解析JSON库
1.CSInterface
CSInterface里有两个方法,可以调用任意的其他C# DLL中的任意公开的静态方法和成员方法,代码放在了Github,传入其他C#的程序集路径或者名称,要调用的方法名称,方法参数,使用反射的方式调用,将实现代码贴出来,很简单:
/// <summary>
/// 加载程序集,assemblyName可以是程序集名称,可以是相对路径、绝对路径(相对于CSInterface的路径)
/// </summary>
/// <param name="assemblyName">可以是相对路径、绝对路径</param>
/// <returns></returns>
private Assembly LoadAssembly(string assemblyName)
{
Assembly asm = null;
if (assemblyName.Contains(".dll"))
{
//说明是路径
if (!Path.IsPathRooted(assemblyName))
{
assemblyName = Path.Combine(GetAppDirectory(), assemblyName);
}
asm = Assembly.LoadFrom(assemblyName);
}
else
{
asm = Assembly.Load(assemblyName);
}
return asm;
}
public object InvokeMemberMethod(string assemblyName, string methodName, bool hasParas,object[] paras)
{
Assembly asm = LoadAssembly(assemblyName);
foreach (var type in asm.GetTypes())
{
if (!type.IsClass)
continue;
//获取类下面的方法
var method = type.GetMethod(methodName);
if (method != null)
{
var obj = Activator.CreateInstance(type);
if (!hasParas)
{
return method.Invoke(obj,null);
}
else
{
return method.Invoke(obj, paras);
}
}
}
return null;
}
public object InvokeStaticMethod(string assemblyName, string methodName,bool hasParas, object[] paras)
{
Assembly asm = LoadAssembly(assemblyName);
foreach (var type in asm.GetTypes())
{
if (!type.IsClass)
continue;
//获取类下面的方法
var method = type.GetMethod(methodName, BindingFlags.InvokeMethod|BindingFlags.Public| BindingFlags.Static);
if (method != null)
{
if (!hasParas)
{
return method.Invoke(null,null);
}
else
{
return method.Invoke(null, paras);
}
}
}
return null;
}
public object CreateInstance(string assemblyName, string className,bool hasParas,object[] paras)
{
Assembly asm = LoadAssembly(assemblyName);
foreach (var type in asm.GetTypes())
{
if (!type.IsClass)
continue;
//获取类下面的方法
if (type.Name == className || type.FullName == className)
{
if (hasParas)
{
return Activator.CreateInstance(type, paras);
}
else
{
return Activator.CreateInstance(type);
}
}
}
return null;
}
public string GetAppDirectory()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
以上,至于为什么InvokeStaticMethod和InvokeMemberMethod方法要带一个hasParas的参数,不能直接给paras=null吗?因为这两个方法是给VB6调用的,在VB6中,最后一个参数设置为Variant的数组,不能等于空传过来,等于空传过来会报错,暂时还没有发现可以替代的方案。
CSInterface要写成COM组件的方式才能注册,关于如何将C#程序集写成COM组件,见参考文章
CSInterface注册,自己写了个工具,可以一键注册CSInterface和VB6的OCX,不用手去敲命令了,注册工具的代码代码见Github,截图如下:
用红线框起来的部分就是注册和卸载CSInterface.DLL用的,注册之后,会在程序目录下生成一个tlb文件,如下图所示:
2.编写一个供VB6调用的任意的DLL
任意的DLL中包含一个静态方法和非静态方法,代码如下:
namespace AnyCSharpDLL
{
public class Class1
{
public static string GetString()
{
return "this is a string from C#";
}
public int Add(int a, int b)
{
return a + b;
}
}
}
将这个DLL(AnyCSharpDLL.dll)编译之后放在CSInterface.dll目录下,如下所示:
打开VB6,新建一个VB6的窗体工程,如下:
引用CSInterface注册之后的tlb文件,打开Project->Reference中,如果注册了,可以找到CSInterface,如下图所示:
将这个勾上
注意:如果注册了之后在Reference中找不到CSInterface,则可能要点击Brower按钮,去找到这个CSInterface.tlb文件,然后就能在Reference中显示了
然后开始编写VB的代码了:
首先在界面上添加两个按钮,分别用来测试两个方法:
编写代码如下:
Dim csWrapper As New CSInterface.csWrapper '实例化一个CSInterface.csWrapper的实例
Private Sub btnGetString_Click()
'定义用于接收结果的字符串
Dim result As String
'定义传入参数,虽然GetString函数不需要传入参数,
'但是InvokeStaticMethod需要传入Object[] 类型的参数,且VB中不能给空,否则会报错
'因为GetString没有参数,所以随便定义包含一个元素的Variant数组传过去就好了
Dim paras(0) As Variant
'调用AnyCSharpDLL.dll中的GetString方法,第三个参数给False表示GetString不需要参数
result = csWrapper.InvokeStaticMethod("AnyCSharpDLL.dll", "GetString", False, paras)
MsgBox result
End Sub
Private Sub btnTestAdd_Click()
Dim result As Integer
Dim paras(0 To 1) As Variant
paras(0) = 5 '第一个参数
paras(1) = 6 '第二个参数
'第三个参数给True表示AnyCSharpDLL.dll中的成员方法Add需要参数
result = csWrapper.InvokeMemberMethod("AnyCSharpDLL.dll", "Add", True, paras)
MsgBox result
End Sub
点击第一个按钮:
点击第二个按钮:
有点遗憾的是写csWrapper点的时候没有任何提示,跟写动态脚本一样,方法有几个参数以及参数的类型需要自己心知肚明,不然容易出错
使用注意事项:
C#返回给VB的必须是常用的数据类型,比如int,string等,如果是复杂的数据类型,建议在C#中序列化成JSON字符串返回给VB,然后在VB中解析JSON,附VB6解析JSON的库