t x, int y); #endif
/* 文件名:lib.cpp #include "lib.h" int add(int x, int y) { return x + y; }
*/
与第 2 节对静态链接库的调用相似,我们也建立一个与 DLL 工程处于同一工作区的应用工 程 dllCall,它调用 DLL 中的函数 add,其源代码如下: #include
#include typedef int(*lpAddFun)(int, int); //宏定义函数指针类型 int main(int argc, char *argv[]) { HINSTANCE hDll; //DLL 句柄 lpAddFun addFun; //
函数指针 hDll = LoadLibrary("..\\Debug\\dllTest.dll"); if (hDll != NULL) { addFun = (lpAddFun)GetProcAddress(hDll, "add"); if (addFun != NULL) { int result = addFun(2, 3); printf("%d", result); } FreeLibrary(hDll); } return 0; } 分析上述代码,dllTest 工程中的 lib.cpp 文件与第 2 节静态链接库版本完全相同,不同 在于 lib.h 对函数 add 的声明前面添加了__declspec(dllexport)语句。 这个语句的含义是声明函
数 add 为 DLL 的导出函数。DLL 内的函数分为两种: (1)DLL 导出函数,可供应用程序调用; (2) DLL 内部函数,只能在 DLL 程序使用,应用程序无法调用它们。 而应用程序对本 DLL 的调用和对第 2 节静态链接库的调用却有较大差异,下面我们来 逐一分析。 首先,语句 typedef int ( * lpAddFun)(int,int)定义了一个与 add 函数接受参数类型和返回 值均相同的函数指针类型。随后,在 main 函数中定义了 lpAddFun 的实例 addFun; 其次,在函数 main 中定义了一个 DLL HINSTANCE 句柄实例 hDll,通过 Win32 Api 函 数 LoadLibrary 动态加载了 DLL 模块并将 DLL 模块句柄赋给了 hDll; 再次, 在函数 main 中通过 Win32 Api 函数 GetProcAddress 得到了所加载 DLL 模块中函 数 add 的地址并赋给了 addFun。经由函数指针 addFun 进行了对 DLL 中 add 函数的调用; 最后,应用工程使用完 DLL 后,在函数 main 中通过 Win32 Api 函数 FreeLibrary 释放 了已经加载的 DLL 模块。 通过这个简单的例子,我们获知 DLL 定义和调用的一般概念: (1)DLL 中需以某种特定的方式声明导出函数(或变量、类) ; (2)应用工程需以某种特定的方式调用 DLL 的导出函数(或变量、类) 。 下面我们来对“特定的方式进行”阐述。 4.2 声明导出函数 DLL 中导出函数的声明有两种方式:一种为 4.1 节例子中给出的在函数声明中加上 __declspec(dllexport), 这里不再举例说明; 另外一种方式是采用模块定义(.def) 文件声明, .def 文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。 下面的代码演示了怎样同.def 文件将函数 add 声明为 DLL 导出函数(需在 dllTest 工程 中添加 lib.def 文件) : ; lib.def : 导出 DLL 函数 LIBRARY dllTest EXPORTS add @ 1
.def 文件的规则为: (1)LIBRARY 语句说明.def 文件相应的 DLL; (2)EXPORTS 语句后列出要导出函数的名称。可以在.def 文件中的导出函数名后加@n, 表示要导出函数的序号为 n(在进行函数调用时,这个序号将发挥其作用) ; (3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一 行。 由此可以看出,例子中 lib.def 文件的含义为生成名为“dllTest”的动态链接库,导出其中 的 add 函数,并指定 add 函数的序号为 1。
4.3 DLL 的调用方式 在 4.1 节
的例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统 Api 提供 的三位一体“DLL 加载-DLL 函数地址获取-DLL 释放”方式,这种调用方式称为 DLL 的动态 调用。 动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。 与动态调用方式相对应的就是静态调用方式,“有动必有静”,这来源于物质世界的对立 统一。“动与静”,其对立与统一竟无数次在技术领域里得到验证,譬如静态 IP 与 DHCP、 静态路由与动态路由等。从前文我们已经