VC++写DLL给VB使用

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2009/08/09/provide-api-for-vb-from-c/

Overview

之前用VC写了一个DLL封装了MySQL的CAPI给VB使用。

在写DLL时遇到了一箩筐问题,现在总结了一些经验,特地写在这里记下来以免遗忘。

准备

测试环境是:VC6.0 简体中文企业版,VB6.0 简体中文企业版,Windows XP Professional SP3 简体中文版

用VC++建立一个Win32 DLL工程(Win32 Dynamic-Link Library),命名为“MyDll1”,注意选择“空工程”。

恩,好了,下面添加文件。按下Ctrl + N,添加一个头文件,命名“MyDll1.h”,再添加一个源文件,命名为“MyDll1.cpp”。

好了?不,还差一个文件,小巧精悍的*.def文件,这个是在向导里选择“文本文件”,然后名字必须是“*.def”格式,一字不漏。

首先打开MyDll1.h,添加第一行代码:

1#include <windows.h>

接着是在MyDll1.cpp里添加

1#include "MyDll1.h"

开始吧~

第一个标准函数

 1//在MyDll1.h里添加
 2int WINAPI Add(int a, int b); //呵呵,现在可不是写Hello World!了
 3
 4//注意格式必须是:函数类型 WINAPI 函数名(参数表)
 5
 6//在MyDll1.cpp里添加
 7int WINAPI Add(int a, int b)
 8{
 9    return int(a + b);
10}

然后在MyDll1.def里写

1LIBRARY            "MyDll1"
2EXPORTS            Add

其中MyDll1是链接库的名称,当然你可以随便写。

Def文件定义了Dll的导出表,用“EXPORTS 函数名称”格式,一行一个函数。而且函数必须用WINAPI声明(包括定义也是)。

保存,按下F7(组建),就会生成一个Dll了,这里直接使用了Win32 Release配置,生成发布版本。

在工程目录的Release文件夹里找到MyDll1.dll,复制到 C:\Windows\System32\ 下。

下面是VB部分。

启动VB,创建“标准EXE”工程,双击窗体,输入代码:

1Private Declare Function Add Lib "MyDll1.dll" (ByVal a As Long, ByVal b As Long) As Long
2'//注意声明方式,后面会深入探讨。
3
4Private Sub Form_Load()
5    MsgBox Add(1, 2)
6End Sub

按下 F5,运行。结果是什么?如无意外应该是 3。

引用形参(地址传递)

第一步测试完成了,第二个问题——引用传递。

想必大家都知道VB函数的参数声明有一个 ByRef 声明前缀类型吧?是什么意思呢?运行下面一个程序你就知道了。

 1Private Function Func1(ByRef a As Long, ByVal b As Long) As Long
 2
 3    a = a + b
 4    Func1 = a
 5
 6End Function
 7
 8Private Sub Form_Load()
 9
10    Dim i As Long
11    i = 123
12    Msgbox Func1(i, 5)
13    Msgbox i
14
15End Sub

ByVal 是值传递,ByRef 是地址传递。

C++也有引用,但这里可不用用所谓意义上的引用,应该用指针。C++的内存世界丰富多彩,指针是开启内存世界大门的金钥匙。

下面用C++实现和上面Func1完全一样的函数给VB调用。

1//在MyDll1.h里添加
2int WINAPI AddEx(int *a, int b);
3
4//在MyDll1.cpp里添加
5int WINAPI AddEx(int *a, int b)
6{
7    return (*a += b);
8}

在MyDll1.def里添加

1EXPORTS            AddEx

保存,按下F7,再次复制到 C:\Windows\System32\

回到VB,使用下面的代码:

 1Private Declare Function AddEx Lib "MyDll1.dll" (ByRef a As Long, ByVal b As Long) As Long
 2'ByRef调用方式
 3Private Sub Form_Load()
 4
 5    Dim i As Long
 6    i = 123
 7    MsgBox AddEx(i, 5) '改了名字而已,调用方式一致。
 8    MsgBox i
 9
10End Sub

按下F5,运行,结果是否和刚才的一样?

空类型函数(void-type-function)

VB中有一种“过程”,它没有返回值,相当于C/C++中的 void 型函数。例如:

1Private Sub Sub1()
2
3    Msgbox "Sub1!"
4
5End Sub

在C/C++里是这样定义的

1void func1()
2{
3    cout << "func1\n";
4}

用VC写一个给VB用下试试。

1//在MyDll1.h里添加
2void WINAPI NAdd(int *a, int b);
3
4//在MyDll1.cpp里添加
5void WINAPI NAdd(int *a, int b)
6{
7    *a += b;
8}

在MyDll1.def里添加

1EXPORTS            NAdd

回到VB,使用如下代码:

 1Private Declare Sub NAdd Lib "MyDll1.dll" (ByRef a As Long, ByVal b As Long)
 2'Sub声明方式,不是Function哦
 3Private Sub Form_Load()
 4
 5    Dim i As Long
 6    i = 123
 7    NAdd i, 5
 8    MsgBox i
 9
10End Sub

自定义类型

好了,传递方式都搞清楚了,不过上面传递的都是基本类型,如果是自定义类型怎么办?例如有:

1Private Type mytype
2    i As Long
3    b As Long
4End Type

如何传递?很简单,回到VC,开始第四步实践。

 1//在MyDll1.h里添加
 2struct mytype //当然得有相同的类型
 3{
 4    int i;
 5    int b;
 6};
 7void    WINAPI    TypeTest(mytype *a); //采用地址传递方式,为了方面就用了空类型函数了。
 8
 9//在MyDll1.cpp里添加
10void WINAPI TypeTest(mytype *a)
11{
12    a->b = 123;
13    a->i = 321;
14}

在MyDll1.def里添加

1EXPORTS            TypeTest

VB程序如下:

 1Private Declare Sub TypeTest Lib "MyDll1.dll" (ByRef a As mytype)
 2Private Type mytype
 3    i As Long
 4    b As Long
 5End Type
 6
 7Private Sub Form_Load()
 8
 9    Dim x As mytype
10    TypeTest x
11
12    MsgBox x.b
13    MsgBox x.i
14
15End Sub

回调函数

回调函数是什么这里做下简单介绍:

每个函数都有一个地址,通过这个地址就可以直接或间接调用函数了。现在假设有函数A,你把函数A的地址以参数的形式传递给函数B,然后函数B又通过这个地址调用函数A,此时函数A就是“回调函数”了。回调函数最常见的形式就是排序函数了。

C++中,函数名就是一个指针,指向这个函数的地址。VB呢?VB没有指针概念啊!别急,有一个 AddressOf 操作符呢。

AddressOf 操作符可以获取一个全局函数(注意,必须是在模块中声明的 Public 全局函数)的地址。

例如有一个全局函数FuncA,那么 AddressOf FuncA 就可以获取它的地址了。

下面开始实践。

1//在MyDll1.h里添加
2typedef int (*myfunc)(); //函数指针类型
3int WINAPI GetBack(myfunc tp);
4
5//在MyDll1.cpp里添加
6int WINAPI GetBack(myfunc tp)
7{
8    return tp(); //调用函数,并返回结果
9}

在MyDll1.def里添加

1EXPORTS            GetBack

回到VB,添加一个模块,在模块里写:

1Public Function func1() As Long
2    func1 = 123
3End Function

再在窗体里写:

1Private Declare Function GetBack Lib "MyDll1.dll" (ByVal a As Long) As Long
2
3Private Sub Form_Load()
4
5    MsgBox GetBack(AddressOf func1)
6
7End Sub

执行查看效果。

智能字符串

最后介绍一个API函数SysAllocString,它可以把C++的字符串(char *)转换成VB的字符串(BSTR),用它就可以写出返回String型的函数了,而且它会在VB中自动释放掉,不需要担心内存泄露问题。

下面是写MySQL API For Visual Basic时用到的:

 1BSTR WINAPI MySQL_Escape_String(const char *tString)
 2{
 3    UINT SLen = strlen(tString);
 4    char *cBuffer = new char[SLen * 2];
 5
 6    if (!mysql_escape_string(cBuffer, tString, SLen)) {
 7
 8        delete []cBuffer;
 9        return NULL;
10    }
11
12    BSTR RTN = SysAllocString((BSTR)cBuffer);
13
14    delete []cBuffer;
15
16    return RTN;
17}

OK,本文就讲到这里了,更多的就大家自己摸索吧。

comments powered by Disqus