第 53 章
的C/C++运行期库能够释放它用于管理多线程应用程序的数据块。
注意:DLL 能够防止线程终止运行。例如,当DllMain()函数接收到DLL_THREAD_DETACH 通
知时,它就能够进入一个无限循环。只有当每个DLL 已经完成对DLL_THREAD_DETACH
通知的处理时,cāo作系统才会终止线程的运行。
如果当DLL 被撤消时仍然有线程在运行, 那么就不会有任何线程调用带有
DLL_THREAD_DETACH 值的DllMain()函数。可以在进行DLL_THREAD_DETACH 的处理
时查看这个情况,这样就能够执行必要的清除cāo作。
10.2.2 DLL 的导出函数
当Microsoft 的C/C++编译器看到变量、函数原型或C++类之前的这个修改符的时候,它
就将某些附加信息嵌入产生的.obj 文件中。当链接DLL 的所有.obj 文件时,链接程序将对这
些信息进行分析。
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·256·
当DLL 被链接时,链接程序要查找关于输出变量、函数或C++类的信息,并自动生成
一个.lib 文件。该.lib 文件包含一个DLL 输出的符号列表。当然,如果要链接引用该DLL 的
输出符号的任何可执行模块,该.lib 文件是必不可少的。除了创建.lib 文件外,链接程序还要
将一个输出符号表嵌入产生的DLL 文件。这个输出节包含输出变量、函数和类符号的列表(按
字母顺序排列)。该链接程序还将能够找到每个符号的相对虚拟地址(RVA),并在该地址中
放入DLL 模块。
使用Microsoft 的Visual Studio 的DumpBin.exe 实用程序(带有-exports 开关),能够看到
DLL 的输出节是个什么样子。Kernel32.dll 的输出结果如图10-1 所示。
图10-1 dumpbin 输出动态链接库的导出函数
通过Visual Studio 所提供的Dependency Walker 的可视化工具也可以查看动态链接库的
导出函数信息,如图10-2 所示。
图10-2 利用Dependency Walker 工具查看导出函数信息
枫叶文学网www.fywxw.com
第10 章 动态链接库
·257·
10.3 两种链接DLL 的方式
如果线程需要调用DLL 模块中的函数,那么DLL 文件映像必须映shè到调用线程的进程
地址空间中。可以用两种方法进行这项cāo作。第一种方法是让应用程序的源代码只引用DLL
中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(或链接)需要的
DLL。第二种方法是在运行时让应用程序显式加载需要的DLL 并且显式链接到需要的输出符
号。换句话说,当应用程序运行时,其中的线程决定它是否要调用DLL 中的函数。该线程可
以将DLL 显式加载到进程的地址空间,获得DLL 中包含的函数的虚拟内存地址,然后使用
该内存地址调用该函数。该方法的一切cāo作都是在应用程序运行时进行的。
当线程加载动态链接库的时候,是按照下面的搜索顺序查找并加载动态链接库文件的。
? 当前目录下(首先将动态链接库拷贝至DEBUG 目录下,因为可执行文件在该目录下)。
? Windows 目录。
? Windows 系统目录。
? PATH 环境变量中设置的目录。
? 列入映shè网络的目录表中的目录。
下面将介绍隐式链接和显式链接这两种调用DLL 的方式。
10.3.1 隐式链接
如果程序员采用隐式链接方式建立一个DLL 文件,链接程序会自动生成一个与之对应的
LIB 导入文件。该文件包含了每一个DLL 导出函数的符号名和可选的标识号,但是并不含有
实际的代码。LIB 文件作为DLL 的替代文件被编译到应用程序项目中。当程序员通过静态链
接方式编译生成应用程序时,应用程序中的调用函数与LIB 文件中导出符号相匹配,这些符
号或标识号进入生成的EXE 文件中。LIB 文件中也包含了对应的DLL 文件名(但不是完全
的路径名),链接程序将其存储在EXE 文件内部。当应用程序运行过程中需要加载DLL 文件
时,Windows 根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL 函数的
动态链接。
下面的例子通过隐式链接调用MyDll.dll 库中的Min 函数。首先生成一个TestDll 项目,
在DllTest.h、DllTest.cpp 文件中分别输入如下代码:
//Dlltest.h
#pragma cocomnt(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include"Dlltest.h"
void main()
{
int a;
a=min(8,10);
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·258·
printf("比较的结果为%d\n",a);
}
在创建DllTest.exe 文件之前,要先将MyDll.dll 和MyDll.lib 拷贝到当前工程所在的目
录下,也可以拷贝到windows 的System目录下。如果DLL 使用的是DEF 文件,要删除TestDll.h
文件中关键字extern "C"。TestDll.h 文件中的关键字Progam commit 是要Visual C++的编译器
在link 时,链接到MyDll.lib 文件。当然,开发人员也可以不使用#pragma cocomnt(lib,
"MyDll.lib")语句,而直接在工程的Setting→Link 页的Object/Moduls 栏填入MyDll.lib 即可。
10.3.2 显式链接
显式链接方式对于集成化的开发语言(例如Visual Basic)比较适合。有了显式链接,程
序员就不必再使用导入文件,而是直接调用Win32 的LoadLibary 函数,并指定DLL 的路径
作为参数。LoadLibary 返回HINSTANCE 参数,应用程序在调用GetProcAddress 函数时使用
这一参数。GetProcAddress 函数将符号名或标识号转换为DLL 内部的地址。假设有一个导出
如下函数的DLL 文件:
extern "C" __declspec(dllexport) double SquareRoot(double d);
在隐式链接方式中,所有被应用程序调用的DLL 文件都会在应用程序EXE 文件加载时
被加载在到内存中。但如果采用显式链接方式,程序员可以决定DLL 文件何时加载或不加载。
显式链接在运行时决定加载哪个DLL 文件。例如,可以将一个带有字符串资源的DLL 模块
以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对
应的DLL 文件。
在显式链接方式中,应用程序在执行过程中随时可以加载DLL 文件,也可以随时卸载
DLL 文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活xìng,更适合解释xìng语
言。不过实现显式链接比较复杂,除了要调用特定的Win32 的LoadLibrary 函数动态链接DLL,
在应用程序退出之前,还应该用FreeLibrary 或MFC 提供的AfxFreeLibrary 释放动态链接库。
下面是通过显式链接调用DLL 中的Max 函数的例子,代码如下:
#include …
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max;
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll 文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll 文件;
}
在上面的程序片断中使用类型定义关键字typedef,定义指向和DLL 中相同的函数原型
枫叶文学网www.fywxw.com
第10 章 动态链接库
·259·
指针,然后通过LoadLibray()将DLL 加载到当前的应用程序中并返回到当前DLL 文件的句
柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针。函数调用完毕后,使
用FreeLibrary()卸载DLL 文件。在编译程序之前,首先要将DLL 文件拷贝到工程所在的目
录或Windows 系统目录下。
使用显式链接应用程序编译时不需要使用相应的LIB 文件。另外,使用GetProcAddress()
函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL 中函数出现的顺序号,如将
GetProcAddress(hDLL,"Min") 改为GetProcAddress(hDLL,MAKEINTRESOURCE(2)) ( 函数
Min()在DLL 中的顺序号是2),这样调用DLL 中函数的速度将会很快,但是要记住函数的使
用序号,否则会发生错误。
10.4 开发DLL
在Visual C++6.0 开发环境下,打开“FileNewProject”选项,可以通过选择Win32
Dynamic-Link Library 或MFC AppWizard[dll]的不同方式来创建Non-MFC Dll、Regular Dll、
Extension Dll 等不同种类的动态链接库。
10.4.1 创建Non-MFC DLL 动态链接库
每一个DLL 必须有一个入口点,这就像用C 编写的应用程序一样,必须有一个WinMain
函数一样。在Non-MFC DLL 中DllMain()是一个默认的入口函数,不需要编写自己的DLL
入口函数,用DllMain()函数就能使动态链接库在被调用时得到正确的初始化。如果应用程序
的DLL 需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除cāo作时,
需要在相应的DLL 工程的CPP 文件中对DllMain()函数按照下面的格式编写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
}
return TRUE;
}
关于DllMain()函数,在10.2.1 节中已经作了详细介绍,这里不再赘述。DLL 是包含若
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·260·
干个函数的库文件,应用程序使用DLL 中的函数之前,应该先导出这些函数,以便供给应用
程序使用。要导出这些函数有两种方法, 一是在定义函数时使用导出关键字
_declspec(dllexport),另外一种方法是在创建DLL 文件时使用模块定义DEF 文件。需要读者
注意的是在使用第一种方法的时候,不能使用DEF 文件。下面通过两个例子来说明使用这两
松语文学免费小说阅读_www.16sy.com
注意:DLL 能够防止线程终止运行。例如,当DllMain()函数接收到DLL_THREAD_DETACH 通
知时,它就能够进入一个无限循环。只有当每个DLL 已经完成对DLL_THREAD_DETACH
通知的处理时,cāo作系统才会终止线程的运行。
如果当DLL 被撤消时仍然有线程在运行, 那么就不会有任何线程调用带有
DLL_THREAD_DETACH 值的DllMain()函数。可以在进行DLL_THREAD_DETACH 的处理
时查看这个情况,这样就能够执行必要的清除cāo作。
10.2.2 DLL 的导出函数
当Microsoft 的C/C++编译器看到变量、函数原型或C++类之前的这个修改符的时候,它
就将某些附加信息嵌入产生的.obj 文件中。当链接DLL 的所有.obj 文件时,链接程序将对这
些信息进行分析。
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·256·
当DLL 被链接时,链接程序要查找关于输出变量、函数或C++类的信息,并自动生成
一个.lib 文件。该.lib 文件包含一个DLL 输出的符号列表。当然,如果要链接引用该DLL 的
输出符号的任何可执行模块,该.lib 文件是必不可少的。除了创建.lib 文件外,链接程序还要
将一个输出符号表嵌入产生的DLL 文件。这个输出节包含输出变量、函数和类符号的列表(按
字母顺序排列)。该链接程序还将能够找到每个符号的相对虚拟地址(RVA),并在该地址中
放入DLL 模块。
使用Microsoft 的Visual Studio 的DumpBin.exe 实用程序(带有-exports 开关),能够看到
DLL 的输出节是个什么样子。Kernel32.dll 的输出结果如图10-1 所示。
图10-1 dumpbin 输出动态链接库的导出函数
通过Visual Studio 所提供的Dependency Walker 的可视化工具也可以查看动态链接库的
导出函数信息,如图10-2 所示。
图10-2 利用Dependency Walker 工具查看导出函数信息
枫叶文学网www.fywxw.com
第10 章 动态链接库
·257·
10.3 两种链接DLL 的方式
如果线程需要调用DLL 模块中的函数,那么DLL 文件映像必须映shè到调用线程的进程
地址空间中。可以用两种方法进行这项cāo作。第一种方法是让应用程序的源代码只引用DLL
中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(或链接)需要的
DLL。第二种方法是在运行时让应用程序显式加载需要的DLL 并且显式链接到需要的输出符
号。换句话说,当应用程序运行时,其中的线程决定它是否要调用DLL 中的函数。该线程可
以将DLL 显式加载到进程的地址空间,获得DLL 中包含的函数的虚拟内存地址,然后使用
该内存地址调用该函数。该方法的一切cāo作都是在应用程序运行时进行的。
当线程加载动态链接库的时候,是按照下面的搜索顺序查找并加载动态链接库文件的。
? 当前目录下(首先将动态链接库拷贝至DEBUG 目录下,因为可执行文件在该目录下)。
? Windows 目录。
? Windows 系统目录。
? PATH 环境变量中设置的目录。
? 列入映shè网络的目录表中的目录。
下面将介绍隐式链接和显式链接这两种调用DLL 的方式。
10.3.1 隐式链接
如果程序员采用隐式链接方式建立一个DLL 文件,链接程序会自动生成一个与之对应的
LIB 导入文件。该文件包含了每一个DLL 导出函数的符号名和可选的标识号,但是并不含有
实际的代码。LIB 文件作为DLL 的替代文件被编译到应用程序项目中。当程序员通过静态链
接方式编译生成应用程序时,应用程序中的调用函数与LIB 文件中导出符号相匹配,这些符
号或标识号进入生成的EXE 文件中。LIB 文件中也包含了对应的DLL 文件名(但不是完全
的路径名),链接程序将其存储在EXE 文件内部。当应用程序运行过程中需要加载DLL 文件
时,Windows 根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL 函数的
动态链接。
下面的例子通过隐式链接调用MyDll.dll 库中的Min 函数。首先生成一个TestDll 项目,
在DllTest.h、DllTest.cpp 文件中分别输入如下代码:
//Dlltest.h
#pragma cocomnt(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include"Dlltest.h"
void main()
{
int a;
a=min(8,10);
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·258·
printf("比较的结果为%d\n",a);
}
在创建DllTest.exe 文件之前,要先将MyDll.dll 和MyDll.lib 拷贝到当前工程所在的目
录下,也可以拷贝到windows 的System目录下。如果DLL 使用的是DEF 文件,要删除TestDll.h
文件中关键字extern "C"。TestDll.h 文件中的关键字Progam commit 是要Visual C++的编译器
在link 时,链接到MyDll.lib 文件。当然,开发人员也可以不使用#pragma cocomnt(lib,
"MyDll.lib")语句,而直接在工程的Setting→Link 页的Object/Moduls 栏填入MyDll.lib 即可。
10.3.2 显式链接
显式链接方式对于集成化的开发语言(例如Visual Basic)比较适合。有了显式链接,程
序员就不必再使用导入文件,而是直接调用Win32 的LoadLibary 函数,并指定DLL 的路径
作为参数。LoadLibary 返回HINSTANCE 参数,应用程序在调用GetProcAddress 函数时使用
这一参数。GetProcAddress 函数将符号名或标识号转换为DLL 内部的地址。假设有一个导出
如下函数的DLL 文件:
extern "C" __declspec(dllexport) double SquareRoot(double d);
在隐式链接方式中,所有被应用程序调用的DLL 文件都会在应用程序EXE 文件加载时
被加载在到内存中。但如果采用显式链接方式,程序员可以决定DLL 文件何时加载或不加载。
显式链接在运行时决定加载哪个DLL 文件。例如,可以将一个带有字符串资源的DLL 模块
以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对
应的DLL 文件。
在显式链接方式中,应用程序在执行过程中随时可以加载DLL 文件,也可以随时卸载
DLL 文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活xìng,更适合解释xìng语
言。不过实现显式链接比较复杂,除了要调用特定的Win32 的LoadLibrary 函数动态链接DLL,
在应用程序退出之前,还应该用FreeLibrary 或MFC 提供的AfxFreeLibrary 释放动态链接库。
下面是通过显式链接调用DLL 中的Max 函数的例子,代码如下:
#include …
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max;
HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll 文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
A=Max(5,8);
printf("比较的结果为%d\n",a);
FreeLibrary(hDLL);//卸载MyDll.dll 文件;
}
在上面的程序片断中使用类型定义关键字typedef,定义指向和DLL 中相同的函数原型
枫叶文学网www.fywxw.com
第10 章 动态链接库
·259·
指针,然后通过LoadLibray()将DLL 加载到当前的应用程序中并返回到当前DLL 文件的句
柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针。函数调用完毕后,使
用FreeLibrary()卸载DLL 文件。在编译程序之前,首先要将DLL 文件拷贝到工程所在的目
录或Windows 系统目录下。
使用显式链接应用程序编译时不需要使用相应的LIB 文件。另外,使用GetProcAddress()
函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL 中函数出现的顺序号,如将
GetProcAddress(hDLL,"Min") 改为GetProcAddress(hDLL,MAKEINTRESOURCE(2)) ( 函数
Min()在DLL 中的顺序号是2),这样调用DLL 中函数的速度将会很快,但是要记住函数的使
用序号,否则会发生错误。
10.4 开发DLL
在Visual C++6.0 开发环境下,打开“FileNewProject”选项,可以通过选择Win32
Dynamic-Link Library 或MFC AppWizard[dll]的不同方式来创建Non-MFC Dll、Regular Dll、
Extension Dll 等不同种类的动态链接库。
10.4.1 创建Non-MFC DLL 动态链接库
每一个DLL 必须有一个入口点,这就像用C 编写的应用程序一样,必须有一个WinMain
函数一样。在Non-MFC DLL 中DllMain()是一个默认的入口函数,不需要编写自己的DLL
入口函数,用DllMain()函数就能使动态链接库在被调用时得到正确的初始化。如果应用程序
的DLL 需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除cāo作时,
需要在相应的DLL 工程的CPP 文件中对DllMain()函数按照下面的格式编写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
}
return TRUE;
}
关于DllMain()函数,在10.2.1 节中已经作了详细介绍,这里不再赘述。DLL 是包含若
枫叶文学网www.fywxw.com
Visual C++ 6.0 程序设计从入门到精通
·260·
干个函数的库文件,应用程序使用DLL 中的函数之前,应该先导出这些函数,以便供给应用
程序使用。要导出这些函数有两种方法, 一是在定义函数时使用导出关键字
_declspec(dllexport),另外一种方法是在创建DLL 文件时使用模块定义DEF 文件。需要读者
注意的是在使用第一种方法的时候,不能使用DEF 文件。下面通过两个例子来说明使用这两
松语文学免费小说阅读_www.16sy.com