VC++写DLL给VB使用
源地址:http://fenying.blog.163.com/blog/static/102055993200979241103/。
之前用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,添加第一行代码:
#include <windows.h>
接着是在MyDll1.cpp里添加
#include "MyDll1.h"
开始吧~
第一个标准函数
//在MyDll1.h里添加
int WINAPI Add(int a, int b); //呵呵,现在可不是写Hello World!了
//注意格式必须是:函数类型 WINAPI 函数名(参数表)
//在MyDll1.cpp里添加
int WINAPI Add(int a, int b)
{
return int(a + b);
}
然后在MyDll1.def里写
LIBRARY "MyDll1"
EXPORTS Add
其中MyDll1是链接库的名称,当然你可以随便写。
Def文件定义了Dll的导出表,用“EXPORTS 函数名称”格式,一行一个函数。而且函数必须用WINAPI声明(包括定义也是)。
保存,按下F7(组建),就会生成一个Dll了,这里直接使用了Win32 Release配置,生成发布版本。
在工程目录的Release文件夹里找到MyDll1.dll,复制到 C:\Windows\System32\
下。
下面是VB部分。
启动VB,创建“标准EXE”工程,双击窗体,输入代码:
Private Declare Function Add Lib "MyDll1.dll" (ByVal a As Long, ByVal b As Long) As Long
'//注意声明方式,后面会深入探讨。
Private Sub Form_Load()
MsgBox Add(1, 2)
End Sub
按下 F5,运行。结果是什么?如无意外应该是 3。
引用形参(地址传递)
第一步测试完成了,第二个问题——引用传递。
想必大家都知道VB函数的参数声明有一个 ByRef
声明前缀类型吧?是什么意思呢?运行下面一个程序你就知道了。
Private Function Func1(ByRef a As Long, ByVal b As Long) As Long
a = a + b
Func1 = a
End Function
Private Sub Form_Load()
Dim i As Long
i = 123
Msgbox Func1(i, 5)
Msgbox i
End Sub
ByVal
是值传递,ByRef
是地址传递。
C++也有引用,但这里可不用用所谓意义上的引用,应该用指针。C++的内存世界丰富多彩,指针是开启内存世界大门的金钥匙。
下面用C++实现和上面Func1完全一样的函数给VB调用。
//在MyDll1.h里添加
int WINAPI AddEx(int *a, int b);
//在MyDll1.cpp里添加
int WINAPI AddEx(int *a, int b)
{
return (*a += b);
}
在MyDll1.def里添加
EXPORTS AddEx
保存,按下F7,再次复制到 C:\Windows\System32\
。
回到VB,使用下面的代码:
Private Declare Function AddEx Lib "MyDll1.dll" (ByRef a As Long, ByVal b As Long) As Long
'ByRef调用方式
Private Sub Form_Load()
Dim i As Long
i = 123
MsgBox AddEx(i, 5) '改了名字而已,调用方式一致。
MsgBox i
End Sub
按下F5,运行,结果是否和刚才的一样?
空类型函数(void-type-function)
VB中有一种“过程”,它没有返回值,相当于C/C++中的 void
型函数。例如:
Private Sub Sub1()
Msgbox "Sub1!"
End Sub
在C/C++里是这样定义的
void func1()
{
cout << "func1\n";
}
用VC写一个给VB用下试试。
//在MyDll1.h里添加
void WINAPI NAdd(int *a, int b);
//在MyDll1.cpp里添加
void WINAPI NAdd(int *a, int b)
{
*a += b;
}
在MyDll1.def里添加
EXPORTS NAdd
回到VB,使用如下代码:
Private Declare Sub NAdd Lib "MyDll1.dll" (ByRef a As Long, ByVal b As Long)
'Sub声明方式,不是Function哦
Private Sub Form_Load()
Dim i As Long
i = 123
NAdd i, 5
MsgBox i
End Sub
自定义类型
好了,传递方式都搞清楚了,不过上面传递的都是基本类型,如果是自定义类型怎么办?例如有:
Private Type mytype
i As Long
b As Long
End Type
如何传递?很简单,回到VC,开始第四步实践。
//在MyDll1.h里添加
struct mytype //当然得有相同的类型
{
int i;
int b;
};
void WINAPI TypeTest(mytype *a); //采用地址传递方式,为了方面就用了空类型函数了。
//在MyDll1.cpp里添加
void WINAPI TypeTest(mytype *a)
{
a->b = 123;
a->i = 321;
}
在MyDll1.def里添加
EXPORTS TypeTest
VB程序如下:
Private Declare Sub TypeTest Lib "MyDll1.dll" (ByRef a As mytype)
Private Type mytype
i As Long
b As Long
End Type
Private Sub Form_Load()
Dim x As mytype
TypeTest x
MsgBox x.b
MsgBox x.i
End Sub
回调函数
回调函数是什么这里做下简单介绍:
每个函数都有一个地址,通过这个地址就可以直接或间接调用函数了。现在假设有函数A,你把函数A的地址以参数的形式传递给函数B,然后函数B又通过这个地址调用函数A,此时函数A就是“回调函数”了。回调函数最常见的形式就是排序函数了。
C++中,函数名就是一个指针,指向这个函数的地址。VB呢?VB没有指针概念啊!别急,有一个 AddressOf
操作符呢。
AddressOf
操作符可以获取一个全局函数(注意,必须是在模块中声明的 Public
全局函数)的地址。
例如有一个全局函数FuncA,那么 AddressOf FuncA
就可以获取它的地址了。
下面开始实践。
//在MyDll1.h里添加
typedef int (*myfunc)(); //函数指针类型
int WINAPI GetBack(myfunc tp);
//在MyDll1.cpp里添加
int WINAPI GetBack(myfunc tp)
{
return tp(); //调用函数,并返回结果
}
在MyDll1.def里添加
EXPORTS GetBack
回到VB,添加一个模块,在模块里写:
Public Function func1() As Long
func1 = 123
End Function
再在窗体里写:
Private Declare Function GetBack Lib "MyDll1.dll" (ByVal a As Long) As Long
Private Sub Form_Load()
MsgBox GetBack(AddressOf func1)
End Sub
执行查看效果。
智能字符串
最后介绍一个API函数SysAllocString,它可以把C++的字符串(char *)转换成VB的字符串(BSTR),用它就可以写出返回String型的函数了,而且它会在VB中自动释放掉,不需要担心内存泄露问题。
下面是写MySQL API For Visual Basic时用到的:
BSTR WINAPI MySQL_Escape_String(const char *tString)
{
UINT SLen = strlen(tString);
char *cBuffer = new char[SLen * 2];
if (!mysql_escape_string(cBuffer, tString, SLen)) {
delete []cBuffer;
return NULL;
}
BSTR RTN = SysAllocString((BSTR)cBuffer);
delete []cBuffer;
return RTN;
}
OK,本文就讲到这里了,更多的就大家自己摸索吧。