在使用动态连接库时,往往提供两个文件:一个引入库(.lib)文件和一个DLL(.dll)文件,其引入库文件(.dll)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。编译时只需要该DLL的引入库文件,该DLL中的函数代码并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。在发布产品时,除了发布可执行文件以外,同时还要发布该程序将要调用的动态连接库。
动态链接库的加载方式分为隐式链接和显示加载。本文以Demo为例,就只谈隐式链接方式,想了解显示加载方式如何使用请参考孙鑫《VC++深入详解》。
应用程序如果想要访问某个DLL中的函数,那么该函数必须是应经被导出的函数。查看dll文件中有哪些导出函数,可以利用Visual Studio提供的命令工具Dumpbin来实现。
命令格式:
dumpbin –exports ***.dll
要导出函数,则需要在将要被导出的函数前面添加标识符:_declspec(dllexpot)。
比如动态链接库项目名为dxDb,在动态链接库中这样写: _declspec int add(int a, int b);
这样函数add就被导出了,编译后将生成一个dxDb.lib库文件和一个dxDb.dll文件。在调用该dll库的add函数时,就需要在使用add函数之前对其做一个声明如下: extern int add(int a, int b);
或者: _declspec(dllimport) int add(int a, int b);
前面加上关键字extern表明函数是在外部定义的,_declspec(dllimport)表明函数是从动态链接库中引入的,这种方式编译器生成的代码效率更高。此时,程序编译会成功通过,但是链接会出现错误,因为虽然对函数进行了声明,但是链接器并不知道add函数是在哪个地方实现的,还要将dxDb.lib文件拷贝到调用程序所在目录下,在工程设置中添加链接dxDb.lib(或者添加语句#pragma comment(lib,"dxDb.lib"),在大熊Demo中该语句在头文件dxDb.h中);然后,为了能让调用程序找到dxDb.dll文件,还需将该文件也拷贝到调用程序所在目录下。 那么问题又来了,一个DLL实现之后通常交给客户端程序,但是客户端程序如何知道该DLL中有哪些导出函数呢?通常在动态链接库编写时,会提供一个头文件,在此文件中提供DLL导出函数原型的声明,以及函数有关注释文档,正如大熊Demo中的dxDb.h头文件所示。
为了使头文件不仅能够为客户端程序服务同时也能够由动态链接库程序本身来使用,dxDb.h头文件在开头加入了如下代码:
#ifdef DXDB_2014_DLL
#else
#define DXDB_2014_DLL _declspec(dllimport)
#pragma comment(lib,"dxDb.lib")
#endif
…
class DXDB_2014_DLL dxDb
…
我们来分析一下,首先使用条件编译指令判断是否定义了DXDB_2014_DLL符号,如果已经定义了,那么不做任何处理;否则定义该符号为_declspec(dllimport),也就是用DXDB_2014_DLL宏代替_declspec(dllimport)。然后我们在动态链接库的源程序dxDb.cpp中首先用#define定义DXDB_2014_DLL宏:#define DXDB_2014_DLL _declspec(dllexport),然后利用#include指令包含dxDb.h头文件。那么我们看一下编译过程,头文件不参与编译,在编译dxDb.cpp文件时,首先定义了DXDB_2014_DLL宏为_declspec(dllexport),然后包含dxDb.dll这一头文件,展开,首先判断DXDB_2014_DLL是否已经定义了,因为此时已定义为_declspec(dllexport),所以不再定义该宏。直接编译后面的dxDb类的声明,class DXDB_2014_DLL dxDb,表明该类是导出的,注意导出类时_declspec(dllexport)要写在class和类名dxDb之间。 之后,将该DLL交给其他使用者(比如说群里其他人)使用时,只要使用者没有定义DXDB_2014_DLL宏(群里使用过Demo的有定义过的吗?显然没有),那么该宏的定义就是_declspec(dllimport),也就是表明类dxDb是导入的。由此可见这种宏定义的巧妙之处。
此外,C++编译器在生成DLL时,会对导出函数进行名字改编,而且不同的编译器使用的改编规则不一样,因此改变后的名字也不一样,此时客户端访问该DLL的导出函数时就会出现问题。那么怎么解决呢?方法有多种,一般会在工程中的模块定义文件(.def,如果没有可以自己添加进去)中用LIBRARY语句来指定动态链接库的内部名称,正如大熊Demo中所写:
LIBRARY
EXPORTS
InitConnect
UnInitConnect
SetConnectParam
GetConnectParam
ConnectDatebase
DisConnectDatebase
DataInsert
DataDelete
DataUpdate
DataQuery
EXPORTS语句的作用是表明DLL将要导出的函数,也就是下面所列的这些函数。
至于大熊Demo中数据库相关的操作和细节,请看《大熊数据库Demo之数据库》。