Fenying

Angus’ Home.


09 Aug 2009

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,本文就讲到这里了,更多的就大家自己摸索吧。

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

comments powered by Disqus