4.1一个简单的dll
第2节给出了以静态链接库方式提供add函数接口的方法,接下来我们来看看怎样用动态链接库实现一个同样功能的add函数。
如图6,在vc++中new一个win32 dynamic-link library工程dlltest(单击此处下载本工程附件)。注意不要选择mfc appwizard(dll),因为用mfc appwizard(dll)建立的将是第5、6节要讲述的mfc 动态链接库。

图6 建立一个非mfc dll
在建立的工程中添加lib.h及lib.cpp文件,源代码如下:
/* 文件名:lib.h */
#ifndef lib_h
#define lib_h
extern 'c' int __declspec(dllexport)add(int x, int y);
#endif
/* 文件名:lib.cpp */
#include 'lib.h'
int add(int x, int y)
{
return x + y;
}
与第2节对静态链接库的调用相似,我们也建立一个与dll工程处于同一工作区的应用工程dllcall,它调用dll中的函数add,其源代码如下:
#include <stdio.h>
#include <windows.h>
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、静态路由与动态路由等。从前文我们已经知道,库也分为静态库与动态库dll,而想不到,深入到dll内部,其调用方式也分为静态与动态。“动与静”,无处不在。《周易》已认识到有动必有静的动静平衡观,《易.系辞》曰:“动静有常,刚柔断矣”。哲学意味着一种普遍的真理,因此,我们经常可以在枯燥的技术领域看到哲学的影子。