动态链接库 (DLL)是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL副本的内容。
1 DLL基本编程原理分析
一般来说,DLL是一种磁盘文件(通常带有.dll扩展名),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows在加载 DLL模块时将进程函数调用与DLL文件的导出函数相匹配。在 Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。
1.1 导出和导入函数的匹配
DLL文件中包含一个导出函数表。这些导出函数由它们的符号名和称为标识号的整数与外界联系起来。函数表中还包含了 DLL中函数的地址。当应用程序加载 DLL模块时,它并不知道调用函数的实际地址,但它知道函数的符号名和标识号。动态链接过程在加载的 DLL模块时动态建立一个函数调用与函数地址的对应表。如果重新编译和重建 DLL文
件,并不需要修改应用程序,除非你改变了导出函数的符号名和参数序列。简单的 DLL文件只为应用程序提供导出函数,比较复杂的 DLL文件除了提供导出函数以外,还调用其它 DLL文件中的函数。
在 DLL代码中,必须像下面这样明确声明导出函数:
__declspec(dllexport) int MyFunction(int n);
但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在应用程序方面,要求像下面这样明确声明相应的输入函数:
__declspec(dllimport) int MyFuncition(int n);
仅有导入和导出声明并不能使应用程序内部的函数调用链接到相应的 DLL文件上。应用程序的项目必须为链接程序指定所需的输入库 (LIB文件)。而且应用程序事实上必须至少包含一个对 DLL函数的调用。
1.2 与 DLL模块建立链接
应用程序导入函数与 DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明 DLL文件的实际存储路径,程序员不需关心 DLL文件的实际装载。而显式链接与此相反。显式链接方式对于集成化的开发语言(例如 VB)比较适合。
1.3 使用符号名链接与标识号链接
在 Win32环境中, Microsoft现在推荐使用符号名链接。但在 MFC库中的 DLL版本仍然采用的是标识号链接。一个典型的 MFC程序可能会链接到数百个 MFC DLL函数上。采用标识号链接的应用程序的 EXE文件体相对较小,因为它不必包含导入函数的长字符串符号名。
1.4 编写
DllMain函数
DllMain函数是 DLL模块的默认入口点。当 Windows 加载 DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数 DLLMain。DLLMain函数不仅在将 DLL链接加载到进程时被调用,在 DLL模块与进程分离时(以及其它时候)也被调用。
1.5 模块句柄
进程中的每个 DLL模块被全局惟一的 32字节的 HINSTANCE句柄标识。进程自己还有一个 HINSTANCE句柄。所有这些模块句柄都只有在特定的进程内部有效,它们代表了 DLL或 EXE模块在进程虚拟空间中的起始地址。在 Win32中,HINSTANCE和 HMODULE的值是相同的,这两种类型可以替换使用。进程模块句柄几乎总是等于0x400000,而 DLL模块的加载地址的缺省句柄是 0x10000000。如果程序同时使用了几个 DLL模块,每一个都会有不同的 HINSTANCE值。这是因为在创建 DLL文件时指定了不同的基地址,或者是因为加载程序对 DLL代码进行了重定位。模块句柄对于加载资源特别重要。 Win32 的 FindResource函数中带有一个 HINSTANCE参数。EXE和
DLL都有其自己的资源。如果应用程序需要来自于 DLL的资源,就将此参数指定为 DLL的模块句柄。如果需要 EXE文件中包含的资源,就指定 EXE的模块句柄。但是在使用
这些句柄之前存在一个问题,你怎样得到它们呢?如果需要得到 EXE模块句柄,调用带有 Null参数的 Win32函数 GetModuleHandle;如果需要 DLL模块句柄,就调用以 DLL文件名为参数的 Win32函数 GetModuleHandle。
2 DLL的实现及其调用
在创建和调用动态链接库时要用到一些函数调用约定。函数调用约定是指决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别
函数名字的修饰约定。
2.1 函数调用约定有多种
(1)__stdcall调用约定相当于 16位动态库中经常使用的 PASCAL调用约定。在 32位的 VC++6.0 中 PASCAL调用约定不再被支持,取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分。_stdcall是 Pascal程序的缺省调用方式,通常用于 Win32 API中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。 VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
(2)C调用约定,按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的。另外,在函数名修饰约定方面也有所不同。_cdecl是
C和 C++程序缺省的调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用 _stdcall 函数的大。函数采用从右到左的压栈方式。 VC将函数编译后会在函数名前面加上下划线前缀。它是 MFC缺省调用约定。
(3)__fastcall 调用约定的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用 ECX和 EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈 ),在函数名修饰约定方面,它和前两者均不同。 _fastcall方式的函数采用寄存器传递参数, VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上 "@"和参数的字节数。
(4)thiscall仅仅应用于"C++"成员函数。
this指针存放于 CX寄存器,参数从右到左压。 thiscall 不是关键词,因此不能被程序员指定。
(5)naked call采用(1)—(4)的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存 ESI,EDI,EBX, EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。 naked call不产生这样的代码。 naked call不是类型修饰符,故必须和_declspec 共同使用。
2.2 创建动态链接库
DLL 在 Visual C++6.0开发环境下,打开 File\New\Project选项,可以选择 Win32 Dynamic Link Library或 MFC AppWizard[dll]来以不同的方式来创建 Non-MFC Dll、Regular Dll、Extension Dll等不同种类的动态链接库。该动态链接库编译成功后,打开 MyDll工程中的 debug目录,可以看到 MyDll.dll、MyDll.lib两个文件。 LIB文件中包含 DLL文件名和 DLL文件中的函数名等,该 LIB文件只是对应该 DLL文件的“映像文件”,与 DLL文件中,LIB文件的长度要小的多,在进行隐式链接
DLL时要用到它。
2.3 动态链接库
DLL的调用
(1)静态调用方式:由编译系统完成对 DLL的加载和应用程序结束时 DLL卸载的编码。如还有其它程序使用该DLL,则 Windows对 DLL的应用记录减 1,直到所有相关程进来,动态连接库的文件名即是上面两个函数的参数,再用 序都结束对该 DLL的使用时才释放它,简单实用,但不够灵GetProcAddress()获取想要引入的函数。自此,你就可以像使活,只能满足一般要求。用如同本应用程序自定义的函数一样来调用此引入函数了。
隐式的调用:需要把产生动态连接库时产生的 LIB文在应用程序退出之前,应该用 FreeLibrary或 MFC提供的 件加入到应用程序的工程中,想使用 DLL中的函数时,AfxFreeLibrary释放动态连接库。直接调用 Win32的 LoadLibary只须说明一下。隐式调用不需要调用 LoadLibrary()和 函数,并指定 DLL的路径作为参数。LoadLibary返回
FreeLibrary()。程序员在建立一个 DLL文件时,链接程序会HINSTANCE参数,应用程序在调用 GetProcAddress函数时自动生成一个与之对应的 LIB导入文件。该文件包含了每一使用这一参数。GetProcAddress 函数将符号名或标识号转换个 DLL导出函数的符号名和可选的标识号,但是并不含有实为 DLL内部的地址。程序员可以决定 DLL文件何时加载或际的代码。LIB文件作为 DLL的替代文件被编译到应用程序不加载,显式链接在运行时决定加载哪个 DLL文件。使用 项目中。DLL的程序在使用之前必须加(LoadLibrary)DLL从而得到当程序员通过静态链接方式编译生成应用程序时,应用一个 DLL模块的句柄,然后调用 GetProcAddress函数得到输程序中的调用函数与 LIB文件中导出符号相匹配,这些符号出函数的指针,在退出之前必须卸载 DLL(FreeLibrary)。 或标识号进入到生成的 EXE文件中。LIB文件中也包含了对3 总结应的 DLL文件名,链接程序将其存储在 EXE文件内部。本文主要介绍了 Windows动态链接库技术的工作机制当应用程序运行过程中需要加载 DLL文件时, Windows 优点,通过举例分析了动态链接库的创建,及其调用方式等根据这些信息发现并加载 DLL,然后通过符号名或标识号实方面,为利用动态链接库解决工程中问题提供了方便快捷的现对 DLL函数的动态链接。所有被应用程序调用的 DLL文方法,动态链接库也是开发应用程序技术保密的有效手段,件都会在应用程序 EXE文件加载时被再加载到内存中。可执希望本文能为工程研究方面提供一定的技术支持。行程序链接到一个包含 DLL输出函数信息的输入库文件。操作系统在加载使用可执行程序时加载 DLL。可执行程序直接通过函数名调用 DLL的输出函数,调用方法和程序内部其它的函数是一样的
(2)动态调用方式:是由编程者用 API函数加载和卸载 DLL来达到调用 DLL的目的,使用上较复杂,但能更加有[效地使用内存,是编制大型应用程序时的重要方式。显式的调用:是指在应用程序中用 LoadLibrary或 AfxLoadLibrary显式的将自己所做的动态连接库调用进来,动态链接库的文件名即是上面两个函数的参数,再用GetProcAddress()获取想要引入的参数。自此,你就可以像使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary 或MFC提供的AfxFreeLibrary释放动态链接库。直接调用Win32的LoadLibrary函数,并指定DLL的路径作为参数。LoadLibrary返回HINSTANCE参数,应用程序在调用GetProcAddress函数是使用这一参数,应用程序在调用GetProcAddress函数是使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。程序员可以决定DLL文件何时加载或不加载,显示连接在运行时决定加载哪个DLL文件。使用DLL的程序在使用之前必须加载(LoadLibrary)DLL从而得到一个DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,在退出啊之前必须卸载DLL(FreeLibrary)。
3总结
本文主要介绍了Windows动态链接库技术的工作机制有点,通过举例分析了动态链接库的创建,以及调用方式等方面,为利用动态链接库解决工程中问题提供方面快捷的方法,动态链接库也是开发应用程序技术保姆的有效手段,希望本文能为工程研究方面提供一定的技术支持。