动态链接库技术及其研究

2010.8

79

动态链接库技术及其研究

李谋平

安庆师范学院 安徽 246011

摘要:本文深入分析了Windows 动态链接库(DLL)工作原理,阐述了DLL 的优点等特性,通过简单实例,探讨了创建和调用动态链接库的方法,为工程人员开发和使用动态链接库提供了一定的技术支持。

关键词:动态链接库;动态调用;静态调用

0 前言

动态链接库(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

2010.8

80 加载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 卸载的编码。如还有其它程序使用该

2010.8

81

DLL ,则Windows 对DLL 的应用记录减1,直到所有相关程序都结束对该DLL 的使用时才释放它,简单实用,但不够灵活,只能满足一般要求。

隐式的调用:需要把产生动态连接库时产生的LIB 文件加入到应用程序的工程中,想使用 DLL 中的函数时,只须说明一下。隐式调用不需要调用LoadLibrary()和

FreeLibrary()。程序员在建立一个DLL 文件时,链接程序会自动生成一个与之对应的LIB 导入文件。该文件包含了每一个DLL 导出函数的符号名和可选的标识号,

但是并不含有实际的代码。LIB 文件作为DLL 的替代文件被编译到应用程序项目中。

当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB 文件中导出符号相匹配,这些符号或标识号进入到生成的EXE 文件中。LIB 文件中也包含了对应的DLL 文件名,链接程序将其存储在EXE 文件内部。

当应用程序运行过程中需要加载DLL 文件时,Windows 根据这些信息发现并加载 DLL ,然后通过符号名或标识号实现对DLL 函数的动态链接。所有被应用程序调用的DLL 文件都会在应用程序EXE 文件加载时被再加载到内存中。可执行程序链接到一个包含DLL 输出函数信息的输入库文件。操作系统在加载使用可执行程序时加载DLL 。可执行程序直接通过函数名调用DLL 的输出函数,调用方法和程序内部其 它的函数是一样的。

(2)动态调用方式:是由编程者用API 函数加载和卸载

DLL 来达到调用DLL 的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。

显式的调用:是指在应用程序中用LoadLibrary 或MFC 提供的AfxLoadLibrary 显式的将自己所做的动态连接库调

进来,动态连接库的文件名即是上面两个函数的参数,再用

GetProcAddress()获取想要引入的函数。自此,你就可以像使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用 FreeLibrary 或MFC 提供的

AfxFreeLibrary 释放动态连接库。直接调用Win32的LoadLibary 函数,并指定DLL 的路径作为参数。LoadLibary 返回

HINSTANCE 参数,应用程序在调用GetProcAddress 函数时使用这一参数。GetProcAddress 函数将符号名或标识号转换为DLL 内部的地址。程序员可以决定DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个DLL 文件。使用

DLL 的程序在使用之前必须加载(LoadLibrary)DLL 从而得到一个DLL 模块的句柄,

然后调用GetProcAddress 函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。

3 总结

本文主要介绍了Windows 动态链接库技术的工作机制优点,通过举例分析了动态链接库的创建,及其调用方式等方面,为利用动态链接库解决工程中问题提供了方便快捷的方法,动态链接库也是开发应用程序技术保密的有效手段,希望本文能为工程研究方面提供一定的技术支持。

参考文献

[1](美)杰夫瑞(Jeffrey,J.),(法)克里斯托夫(Christophe,N.)著. Windows 核心编程.清华大学出版社. 2008.

[2]Lubomir F. Bic,Alan C.Shaw.著.操作系统原理.清华大学出版社.2005.

[3]吴金平等. Visual c++ 6.0编程与实践.中国水利水电出版社. 2004.

[4]熊华,刘凤新,潘小莉.Windows 动态链接库原理分析及其应用.北京化工大学学报.2004.

The technology of Dynamic Link Library and The Research on It Li Mouping

Anqing normal college,Anhui,246011,China

Abstract:The paper deeply analyses the principle of DLL of windows. It shows the advantages of DLL and the way of creating and calling DLL by simple examples. It provides some technology support for people to develop and use DLL.

Keywords:Dynamic Link Library(DLL);Dynamic call;static call

相关文档
最新文档