DLL

来自站长百科
跳转至: 导航、​ 搜索

动态链接库(Dynamic Link Library或者Dynamic-link library,缩写为DLL),又称为动态连结库,是微软公司在微软视窗操作系统中实现共享函数库概念的一种实作方式。这些库函数的扩展名是.DLL、.OCX(包含ActiveX控制的库)或者.DRV(旧式的系统驱动程序)。

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

DLL的文档格式与视窗EXE文档一样——也就是说,等同于32位视窗的可移植执行文档(PE)和16位视窗的New Executable(NE)。作为EXE格式,DLL可以包括源代码、数据和资源的多种组合。

在更广泛的意义上说,任何同样文档格式的电脑文件都可以称作资源DLL。这样的DLL的例子有有扩展名ICL为的图标库、扩展名为FON和FOT的字体文档。

背景[ ]

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

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

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

尽管有这么多的优点,使用DLL也有一个缺点:DLL地狱,也就是几个应用程序在使用同一个共享DLL库发生版本冲突。这样的冲突可以通过将不同版本的问题DLL放到应用程序所在的文件夹而不是放到系统文件夹来解决;但是,这样将抵消共享DLL节约的空间。目前,Microsoft .NET将解决DLL 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注销此计数器。 D LL文档能够在运行时使用LoadLibrary(或者LoadLibraryEx)API函数进行显式调用,这个的过程微软简单地称为运行时动态调用。API函数GetProcAddress根据查找输出名称符号、FreeLibrary卸载DLL。这些函数类似于POSIX标准API中的dlopen、dlsym、和dlclose。

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

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

导出DLL函数[ ]

要导出 DLL 函数,您可以向导出的 DLL 函数中添加函数关键字,也可以创建模块定义文件 (.def) 以列出导出的 DLL 函数。两种方法

方法一、向导出的 DLL 函数中添加函数关键字

要使用函数关键字,您必须使用以下关键字来声明要导出的各个函数:

__declspec(dllexport)

要在应用程序中使用导出的 DLL 函数,您必须使用以下关键字来声明要导入的各个函数:

__declspec(dllimport)

通常情况下,您最好使用一个包含 define 语句和 ifdef 语句的头文件,以便分隔导出语句和导入语句。

方法二、创建模块定义文件 (.def) 以列出导出的 DLL 函数

使用模块定义文件来声明导出的 DLL 函数。当您使用模块定义文件时,您不必向导出的 DLL 函数中添加函数关键字。在模块定义文件中,您可以声明 DLL 的 LIBRARY 语句和 EXPORTS 语句。

特别调用[ ]

关于特定情况下的调用,比如DLL函数中使用到了win32 API或者将C++生成的DLL供标准C语言使用,则需要注意以下一些情况:

如果使用到了win32 API,则应该使用调用方式为“__stdcall”。

在将C++生成的DLL供标准C语言使用,输出文件需要用“extern "C"”修饰,否则不能被标准C语言调用。如果使用“__stdcall”调用方式,可能产生C不识别的修饰名,所以设置导出函数时要采用.def文件形式,而不是__declspec(dllexport)形式。后者会进行修饰名转换,C语言无法识别函数。

下面的代码是一个定义文件的示例。   // SampleDLL.def   //   LIBRARY "sampleDLL"   EXPORTS HelloWorld示例 DLL 和应用程序XXXXXXXX 在 Microsoft Visual C++ 6.0 中,可以通过选择“Win32 动态链接库”项目类型或“MFC 应用程序向导 (dll)”来创建 DLL。下面的代码是一个在 Visual C++ 中通过使用“Win32 动态链接库”项目类型创建的 DLL 的示例。

// SampleDLL.cpp
  //#include "stdafx.h"
  #define EXPORTING_DLL
  #include "sampleDLL.h"
  BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
  {
  return TRUE;
  }
  void HelloWorld(){
  MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
  }
  // File: SampleDLL.h
  //#ifndef INDLL_H
  #define INDLL_H
  #ifdef EXPORTING_DLLextern __declspec(dllexport) void HelloWorld() ;
  #elseextern __declspec(dllimport) void HelloWorld() ;
  #endif
  #endif

下面的代码是一个“Win32 应用程序”项目的示例,该示例调用 SampleDLL DLL 中的导出 DLL 函数。

// SampleApp.cpp
  //#include "stdafx.h"
  #include "sampleDLL.h"
  int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
  {
  HelloWorld();
  return 0;
  }

注意:在加载时动态链接中,您必须链接在生成 SampleDLL 项目时创建的 SampleDLL.lib 导入库。

DLL内容说明[ ]

  • KERNEL32.DLL ---- 低级内核函数。使用他可以完成内存管理、任务管理、资源控制等。
  • USER32.DLL------于windows管理有关的函数。消息、菜单、光标、计时器、通信和其他大多数非现实函数都可以从这里找到
  • GDI32.DLL-------图形设备接口库。于设备输出有关的函数:大多数绘图、显示场景、图元文件、坐标及其字体函数都可以从这里找到。
  • COMDLG32.DLL\LZ32.DLL\VERSION.DLL\---这都是提供一些附加函数的库,包括通用对话框、文件压缩、版本控制的支持。
  • COMCTL32.DLL --------一个新的windows控件集合,比如TreeView和RichTextBox等等,最初这个是为了win95而制作的,但是现在也使用与NT下
  • MAPI32.DLL---------提供了一套电子邮件的专用函数
  • NETAPI32.DLL--------提供了一套访问和控制网络的函数
  • ODBC32.DLL--------ODBC功能的DLL之一
  • WINMM.DLL--------是多媒体控制访问函数集合
  • DLL(Delay Locked Loop,延时锁定回路提供一个数据滤波信号)

相关条目[ ]

参考来源[ ]