动态链接库

  动态链接库是一个可以执行的并可以被多个Windows应用程序共享的程序模块,模块中包含了一些可以被多个Windows应用程序或DLL共享的代码、数据和资源。

概述

  所谓动态链接,就是把一些经常会共用的代码(静态链接的OBJ程序库)制作成DLL档,当可执行文件调用到DLL档内的函数时,windows操作系统才会把DLL档加载存储器内,DLL档本身的结构就是可可执行文件,当程序需求函数才进行链接。通过动态链接方式,存储器浪费的情形将可大幅降低。

背景

  动态链接库的最初目的是节约应用程序所需的磁盘和内存空间。在一个传统的非共享库中,一部分代码简单地附加到调用的程序上。如果两个程序调用同一个子程序,就会出现两份那段代码。相反,许多应用共享的代码能够切分到一个动态链接库中,在硬盘上存为一个文档,在内存中使用一个实例(instance)。动态链接库的广泛应用使得早期的视窗能够运行在紧巴巴的内存条件下。

  动态链接库提供了如模块化这样的共享库的普通好处。模块化允许仅仅更改几个应用程序共享使用的一个动态链接库中的代码和数据而不需要更改应用程序自身。这种模块化的基本形式允许如Microsoft Office、Microsoft Visual Studio、甚至微软视窗自身这样大的应用程序使用较为紧凑的补丁和服务包。

  模块化的另外一个好处是插件的通用接口使用。单个的接口允许旧的模块与新的模块一样能够与以前的应用程序运行时无缝地集成到一起,而不需要对应用程序本身作任何更改。这种动态扩展的思想在ActiveX中发挥到了。

  尽管有这么多的优点,使用动态链接库也有一个缺点:动态链接库地狱,也就是几个应用程序在使用同一个共享动态链接库库发生版本冲突。这样的冲突可以通过将不同版本的问题动态链接库放到应用程序所在的文件夹而不是放到系统文件夹来解决;但是,这样将抵消共享动态链接库节约的空间。目前,Microsoft .NET将解决动态链接库 hell问题当作自己的目标,它允许同一个共享库的不同版本并列共存。由于现代的计算机有足够的磁盘空间和内存,这也可以作为一个合理的实现方法。

特征

  内存管理

  在Win32中,DLL文档按照片段(sections)进行组织。每个片段有它自己的属性,如可写或是只读、可执行(代码)或者不可执行(数据)等等。

  DLL代码段通常被使用这个DLL的进程所共享;也就是说它们在物理内存中占据一个地方,并且不会出现在页面文档中。如果代码段所占据的物理内存被收回,它的内容就会被放弃,后面如果需要的话就直接从DLL文档重新加载。

  与代码段不同,DLL的数据段通常是私有的;也就是说,每个使用DLL的进程都有自己的DLL数据副本。作为选择,数据段可以设置为共享,允许通过这个共享内存区域进行进程间通信。但是,因为用户权限不能应用到这个共享DLL内存,这将产生一个安全漏洞;也就是一个进程能够破坏共享数据,这将导致其它的共享进程异常。例如,一个使用访客账号的进程将可能通过这种方式破坏其它运行在特权账号的进程。这是在DLL中避免使用共享片段的一个重要原因。

  当DLL被如UPX这样一个可执行的packer压缩时,它的所有代码段都标记为可以读写并且是非共享的。可以读写的代码段,类似于私有数据段,是每个进程私有的并且被页面文档备份。这样,压缩DLL将同时增加内存和磁盘空间消耗,所以共享DLL应当避免使用压缩DLL。

  符号解析和绑定

  DLL输出的每个函数都由一个数字序号标识,也可以由可选的名字标识。同样,DLL引入的函数也可以由序号或者名字标识。对于内部函数来说,只输出序号的情形很常见。对于大多数视窗API函数来说名字是不同视窗版本之间保留不变的;序号有可能会发生变化。这样,我们不能根据序号引用视窗API函数。

  按照序号引用函数并不一定比按照名字引用函数性能更好:DLL输出表是按照名字排列的,所以对半查找可以用来在在这个表中根据名字查找这个函数。另外一方面,只有线性查找才可以用于根据序号查找函数。

  将一个可执行文件绑定到一个特定版本的DLL也是可能的,这也就是说,可以在编译时解析输入函数(imported functions)的地址。对于绑定的输入函数,连结工具保存了输入函数绑定的DLL的时间戳和校验和。在运行时Windows检查是否正在使用同样版本的库,如果是的话,Windows将绕过处理输入函数;否则如果库与绑定的库不同,Windows将按照正常的方式处理输入函数。

  绑定的可执行文件如果运行在与它们编译所用的环境一样,函数调用将会较快,如果是在一个不同的环境它们就等同于正常的调用,所以绑定输入函数没有任何的缺点。例如,所有的标准Windows应用程序都绑定到它们各自的Windows发布版本的系统DLL。将一个应用程序输入函数绑定到它的目的环境的好机会是在应用程序安装的过程。

  运行时显式链接

  对每个DLL来说,Windows存储了一个全局计数器,每多一个进程使用便多额外一个。LoadLibrary与FreeLibrary指令影响每一个进程内含的计数器;动态连结则不影响。因此借由调用FreeLibrary多次,从存储器反加载一DLL是很重要的。一个进程可以从它自己的VAS注销此计数器。

  DLL文档能够在运行时使用LoadLibrary(或者LoadLibraryEx)API函数进行显式调用,这个的过程微软简单地称为运行时动态调用。API函数GetProcAddress根据查找输出名称符号、FreeLibrary卸载DLL。这些函数类似于POSIX标准API中的dlopen、dlsym、和dlclose。

  注意微软简单称为运行时动态链接的运行时隐式链接,如果不能找到链接的DLL文档,Windows将提示一个错误消息并且调用应用程序失败。应用程序开发人员不能通过编译链接来处理这种缺少DLL文档的隐式链接问题。另外一方面,对于显式链接,开发人员有机会提供一个完善的出错处理机制。

  运行时显式链接的过程在所有语言中都是相同的,因为它依赖于Windows API而不是语言结构。

优点

  (1)应用范围广。

  (2)便于开发大型软件。

  (3)便于对系统进行升级。

  (4)隐藏细节。

  (5)独立于编程语言。

相关百科