VC++写DLL给VB使用
该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2009/08/09/provide-api-for-vb-from-c/
文章目录
之前用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,本文就讲到这里了,更多的就大家自己摸索吧。