什么是动态链接库?
一、动态链接库的概念
动态链接库(Dynamic Link Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。动态链接库文件的扩展名一般是dll, 也有可能是drv、sys和fon,它和可执行文件(exe)非常类似,区别在于DLL中虽然包含了可执行代码却不能单独执行,而应由Windows应用 程序直接或间接调用。
动态链接是相对于静态链接而言的。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过 程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵 的内存资源。而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。 仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接 产生的重定位信息,Windows才转去执行DLL中相应的函数代码。
一般情况下,如果一个应用程序使用了动态链接库,Win32系统保证内存中只有DLL的一份复制品,这是通过内存映射文件实现的。DLL首先 被调入Win32系统的全局堆栈,然后映射到调用这个DLL的进程地址空间。在Win32系统中,每个进程拥有自己的32位线性地址空间,如果一个DLL 被多个进程调用,每个进程都会收到该DLL的一份映像。与16位Windows不同,在Win32中DLL可以看作是每个进程自己的代码。
二、动态链接库的优点
1. 共享代码、资源和数据
使用DLL的主要目的就是为了共享代码,DLL的代码可以被所有的Windows应用程序共享。
2. 隐藏实现的细节
DLL中的例程可以被应用程序访问,而应用程序并不知道这些例程的细节。
3. 拓展开发工具如Delphi的功能
由于DLL是与语言无关的,因此可以创建一个DLL,被C++、VB或任何支持动态链接库的语言调用。这样如果一种语言存在不足,就可以通过访问另一种语言创建的DLL来弥补。
三、动态链接库的实现方法
1. Load-time Dynamic Linking
这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含DLL函数的代码;当程序执行时,利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中,其主要目的是便于代码共享。
2. Run-time Dynamic Linking
这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要决定应调用哪个函数,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。
教你认识动态链接库DLL文件
DLL是Dynamic Link Library的缩写,意为动态链接库。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL 文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可有多个DLL文件,一个DLL文件也可能被几个应用程序所共 用,这样的DLL文件被称为共享DLL文件。DLL文件一般被存放在C:WindowsSystem目录下。
1、如何了解某应用程序使用哪些DLL文件
右键单击该应用程序并选择快捷菜单中的“快速查看”命令,在随后出现的“快速查看”窗口的“引入表”一栏中你将看到其使用DLL文件的情况。
2、如何知道DLL文件被几个程序使用
运行Regedit,进入HKEY_LOCAL_MACHINESoftwareMicrosrftWindowsCurrent-
VersionSharedDlls子键查看,其右边窗口中就显示了所有DLL文件及其相关数据,其中数据右边小括号内的数字就说明了被几个程序使用,(2)表示被两个程序使用,(0)则表示无程序使用,可以将其删除。
3、如何解决DLL文件丢失的情况
有时在卸载文件时会提醒你删除某个DLL文件可能会影响其他应用程序的运行。所以当你卸载软件时,就有可能误删共享的DLL文件。一旦出现了 丢失DLL文件的情况,如果你能确定其名称,可以在Sysbckup(系统备份文件夹)中找到该DLL文件,将其复制到System文件夹中。如果这样不 行,在电脑启动时又总是出现“***dll文件丢失……”的提示框,你可以在“开始/运行”中运行Msconfig,进入系统配置实用程序对话框以后,单 击选择“System.ini”标签,找出提示丢失的DLL文件,使其不被选中,这样开机时就不会出现错误提示了。
什么是DLL
DLL是一个包含可由多个程序同时使用的代码和数据的集合。例如,在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。
通过使用DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计帐程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。
DLL的优点
1. 使用较少的资源。当多个程序使用同一个函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其他在Windows操作系统上运行的程序。
2. 简化部署和安装。当DLL中的函数需要更新或修复时,只要函数的参数和返回值没有更改,就不需重新编译或重新建立程序与该DLL的链接。此外,如果多个程序使用同一个DLL,那么多个程序都将从该更新或修复中获益。
3. 支持多语言程序。只要程序遵循函数的调用约定,用不同编程语言编写的程序就可以调用相同的DLL函数。程序与DLL函数在下列方面必须是兼容的:函数期望其参数被推送到堆栈上的顺序,是函数还是应用程序负责清理堆栈,以及寄存器中是否传递了任何参数。
4. 使国际版本的创建轻松完成。通过将资源放到DLL中,创建应用程序的国际版本变得容易得多。可将用于应用程序的每个语言版本的字符串放到单独的DLL资源文件中,并使不同的语言版本加载合适的资源。
DLL的类型(Kinds of DLLs)
Visual C++支持三种类型的DLL,它们分别是Non-MFC DLL、MFC Regular DLL、MFC Extension DLL。
1. Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被MFC或非MFC编写的客户程序调用。
2. Extension DLL支持C++接口,也就是说它导出C++函数或者整个类给客户程序。导出函数可以使用C++或MFC的数据形式作为参数或返回值,当导出整个类时,客 户程序可以创建此类的对象或者从这些类进行派生。使用Extension DLL的一个问题就是该DLL仅能和MFC客户程序一起工作。
3. Regular DLL和上述的Extension Dll一样,也是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有 消息循环)。Regular DLL有一个很大的限制就是,它可以导出C风格的函数,但不能导出C++类、成员函数或重载函数。调用Regular DLL的客户程序不必是MFC应用程序。
它们可以是在Visual C++、Dephi、Visual Basic等编译环境下开发的客户程序。
DLL的加载
客户程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接。
1. 隐式链接(静态加载或加载时动态链接)
为了隐式链接到DLL,客户程序必须从DLL的提供程序获取下列各项:
a. 包含导出函数或C++类声明的头文件(.h文件)
b. 要链接的导入库(.lib文件)
c. 实际的DLL(.dll文件)
使用DLL的客户程序必须include头文件(此头文件包含每个DLL中的导出函数或C++类),并且链接到此DLL的创建者所提供的导入库。
- // Cacl.cpp
- ...
- DLLAPI int Sum(int a, int b)
- {
- return a + b;
- }
- // Cacl.h
- #ifdef CACL_EXPORTS
- #define DLLAPI __declspec(dllexport)
- #else
- #define DLLAPI __declspec(dllimport)
- #endif
- DLLAPI int Sum(int a, int b);
- // Client.cpp
- DLLAPI int sum(int a, int b);
- ...
2. 显式链接(动态加载或运行时动态链接)
在显式链接下,客户程序必须进行函数调用以在运行时显式加载DLL。为显式链接到DLL,客户程序必须:
a. 调用LoadLibrary加载DLL和获取模块句柄
b. 调用GetProcAddress获取指向客户程序要调用的每个导出函数的函数指针(由于客户程序是通过指针调用DLL的函数,编译器不生成外部引用,故无需与导入库链接)
c. 使用完DLL后调用FreeLibrary释放资源
- // Client.cpp
- HINSTANCE hDLL = LoadLibrary("demo");
- if (hDLL != NULL)
- {
- LPFNDLLFUNC1 lpfnDllFunc1 = GetProcAddress(hDLL, "Sum");
- if (lpfnDllFunc1 == NULL)
- {
- FreeLibrary(hDLL);
- return SOME_ERROR_CODE;
- }
- return lpfnDllFunc1(dwParam, uParam);
- }
客户程序如何找到DLL
如果用LoadLibrary显示链接到DLL的话,我们可以指定DLL的全路径名。如果没有指定路径名,或者用了隐式链接,则Windows将使用下面的搜索序列来定位DLL:
1. 包含客户EXE文件的目录
2. 当前目录
3. Windows系统目录(GetSystemDirectory)
4. Windows目录(GetWindowsDirectory)
5. 在Path环境变量里列出的目录(注意:未使用LIBPATH环境变量)
导出DLL函数
DLL文件的布局与EXE文件非常相似,但有一个重要差异:DLL文件包含导出表。导出表包含DLL导出到客户程序的每个函数的名称。只有导出表 中的函数可由客户程序访问。DLL中的任何其他函数都是DLL私有的。通过使用带/EXPORTS选项的Dumpbin工具,可以查看DLL的导出表。
有两种从DLL导出函数的方法:
1. 在函数的定义中使用__declspec(dllexport)关键字
使用__declspec(dllexport)关键字可以从DLL中导出数据、函数、类或类成员函数。如果要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字()的左边(如果指定了关键字)。例如:
- __declspec(dllexport) void __cdecl Function();
若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:
- class __declspec(dllexport) CExampleExport : public CObject
- {
- ...
- };
2. 在生成DLL时,创建一个模块定义(.def)文件并使用此DEF文件。(如果希望按序号而不是按名称从DLL导出函数,则使用此方法。)
示例DLL和客户程序
- // Cacl.h
- #ifdef CACL_EXPORTS
- #define DLLAPI __declspec(dllexport)
- #else
- #define DLLAPI __declspec(dllimport)
- #endif
- DLLAPI int Sum(int a, int b);
- // Cacl.cpp
- #include <windows.h>
- #include "Cacl.h"
- BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
- {
- switch (reason)
- {
- case DLL_PROCESS_ATTACH:
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
- DLLAPI int Sum(int a, int b)
- {
- return a + b;
- }
- // Client.cpp
- #include <stdio.h>
- #include "Cacl.h"
- int main(int argc, char* argv[])
- {
- printf("Sum = %d\n", Sum(5, 3));
- return 0;
- }
如何调试DLL
调试DLL很容易,只要从DLL工程启动调试器即可。第一次这样做的时候,调试器会请求给出客户EXE程序的路径。之后,每次从调试器运行DLL时,调试器会自动装入客户EXE程序,而EXE用搜索序列找到DLL。