动态链接库
动态链接库(Dynamic Link Library 或者 Dynamic-link Library,缩写为 DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。这些库函数的扩展名是 ”.dll"、".ocx"(包含ActiveX控制的库)或者 ".drv"(旧式的系统驱动程序)。
基本信息
- 中文名
动态链接库
- 外文名
Dynamic Link Library
- 缩写
DLL
- 类别
链接库
介绍
动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 文件中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
使用动态链接库可以更为容易地将更新应用于各个模块,而不会影响该程序的其他部分。例如,您有一个大型网络游戏,如果把整个数百MB甚至数GB的游戏的代码都放在一个应用程序里,日后的修改工作将会十分费时,而如果把不同功能的代码分别放在数个动态链接库中,您无需重新生成或安装整个程序就可以应用更新。
动态链接库文件,是一种不可执行的二进制程序文件,它允许程序共享执行特殊任务所必需的代码和其他资源。Windows 提供的DLL文件中包含了允许基于 Windows 的程序在 Windows 环境下操作的许多函数和资源。一般被存放在电脑的"C:\Windows\System32" 目录下。
Windows 中,DLL 多数情况下是带有 ".dll" 扩展名的文件,但也可能是 ".ocx"或其他扩展名;Linux系统中常常是 ".so" 的文件。它们向运行于 Windows操作系统下的程序提供代码、数据或函数。程序可根据 DLL 文件中的指令打开、启用、查询、禁用和关闭驱动程序。
背景
DLL的最初目的是节约应用程序所需的磁盘和内存空间。在一个传统的非共享库中,一部分代码简单地附加到调用的程序上。如果两个程序调用同一个子程序,就会出现两份那段代码。相反,许多应用共享的代码能够切分到一个DLL中,在硬盘上存为一个文件,在内存中使用一个实例(instance)。DLL的广泛应用使得早期的视窗能够在紧张的内存条件下运行。
DLL提供了如模块化这样的共享库的普通好处。模块化允许仅仅更改几个应用程序共享使用的一个DLL中的代码和数据而不需要更改应用程序自身。这种模块化的基本形式允许如Microsoft Office、Microsoft Visual Studio、甚至Microsoft Windows自身这样大的应用程序使用较为紧凑的补丁和服务包。
模块化的另外一个好处是插件的通用接口使用。单个的接口允许旧的模块与新的模块一样能够与以前的应用程序运行时无缝地集成到一起,而不需要对应用程序本身作任何更改。这种动态扩展的思想在ActiveX中发挥到了极致。
尽管有这么多的优点,使用DLL也有一个缺点:DLL地狱,也就是几个应用程序在使用同一个共享DLL库发生版本冲突。这样的冲突可以通过将不同版本的问题DLL放到应用程序所在的文件夹而不是放到系统文件夹来解决;但是,这样将抵消共享DLL节约的空间。目前,Microsoft .NET将解决DLL hell问题当作自己的目标,它允许同一个共享库的不同版本并列共存。由于现代的计算机有足够的磁盘空间和内存,这也可以作为一个合理的实现方法1。
特征
内存管理
在Win32中,DLL文件按照片段(sections)进行组织。每个片段有它自己的属性,如可写或是只读、可执行(代码)或者不可执行(数据)等等。这些section可分为两种,一个是与绝对地址寻址无关的,所以能被多进程公用;另一个是与绝对地址寻址有关的,这个就必须由每个进程有自己的副本专用。sections的这种二分类,在编译DLL时就已经由编译器、链接器给标注好了。所以在装入DLL时,装入器知道哪些sections在内存物理地址空间只需要有一份,供多个进程共用(映射到各个进程的内存逻辑地址空间,所以逻辑地址可以不同); 哪些sections必须是进程使用自己的专用副本。
具体说,DLL装入时需考虑下述情形:
- 1.
局部变量——每个线程都有自己的栈,DLL内部的局部变量随所在函数被执行而在各自线程的调用栈上开辟存储空间。
- 2.
全局变量
- 3.
- 1.
const全局变量——放入const节中,多进程共享;
- 2.
非const全局变量——放入各个进程各自专用的data节中。即DLL装入时各个进程复制一份自己专用的DLL的data节。但是,对于一个进程内的多个线程并发访问这种进程空间全局变量,仍然存在线程安全问题。例如,在一个COM的DLL加载入一个进程的空间后,该进程的多个线程可能会并发访问该COM库的COM对象。为此,Windows与COM引入了线程“套间”(apartment)技术。一个进程内,应用程序与加载的各个DLL分属于不同的Module,如果DLL使用所在Module的全局变量,例如动态链接MFC的regular dll在访问自己的MFC全局变量时,应该明确声明。
- 3.
DLL内部定义的全局变量
- 4.
访问DLL以外定义的全局变量——使用间址技术,在DLL的data节中用一个指针数据类型的内存空间来保存一个外部全局变量的地址。
- 4.
函数调用
- 5.
- 1.
调用DLL内部定义的函数。这不是问题。
- 2.
调用DLL外部定义的函数。例如,DLL内部调用一个外部函数foo()。这个foo函数在进程1中可能实现为“四舍五入”,在进程2中实现为“下取整”。所以调用外部函数是各个进程私用的事情。解决办法是使用间址技术,在data节中用一个“函数指针”数据类型的内存空间来保存这种外部函数的入口地址。
- 6.
跳转指令
- 7.
- 1.
DLL内部跳转,不是问题
- 2.
跳转到DLL外部,解决同上述3.2
DLL代码段通常被使用这个DLL的所有进程所共享。如果代码段所占据的物理内存被收回,它的内容就会被放弃,后面如果需要的话就直接从DLL文件重新加载。
与代码段不同,DLL的数据段通常是私有的;也就是说,每个使用DLL的进程都有自己的DLL数据副本。作为选择,数据段可以设置为共享,允许通过这个共享内存区域进行进程间通信。但是,因为用户权限不能应用到这个共享DLL内存,这将产生一个安全漏洞;也就是一个进程能够破坏共享数据,这将导致其它的共享进程异常。例如,一个使用访客账号的进程将可能通过这种方式破坏其它运行在特权账号的进程。这是在DLL中避免使用共享片段的一个重要原因。
当DLL被如UPX这样一个可执行的packer压缩时,它的所有代码段都标记为可以读写并且是非共享的。可以读写的代码段,类似于私有数据段,是每个进程私有的并且被页面文件备份。这样,压缩DLL将同时增加内存和磁盘空间消耗,所以共享DLL应当避免使用压缩DLL2。