动态链接库

维基百科,自由的百科全书
跳转至: 导航搜索

動態連結函式庫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图标函式庫、扩展名为FONFOT字型文件。

背景[编辑]

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

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

模块化的另外一个好处是插件的通用接口使用。单个的接口允许旧的模块与新的模块一样能够与以前的应用程序运行时无缝地集成到一起,而不需要对应用程序本身作任何更改。这种动态扩展的思想在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註銷此計數器。

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

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

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

编译器和语言考虑[编辑]

Delphi[编辑]

在源文件的开头使用关键词library而不是program,在文件的末尾输出函数使用exports排列。

Delphi不需要LIB文件以从DLL中输入函数。为了链接一个DLL,在函数声明中使用关键词external

微软Visual Basic[编辑]

在Visual Basic(VB)中只支持运行时链接;但是除了使用LoadLibraryGetProcAddress这两个API函数之外,允许使用输入函数的声明来引入DLL函数,如果找不到DLL文件,VB将产生一个运行时异常。开发人员可以捕获该异常并且进行适当的处理。

CC++[编辑]

微软Visual C++(MSVC)提供了许多标准C++的扩展,它允许直接在C++代码中将函数标为输入还是输出函数;这种做法已经被其它的Windows平台的C和C++编译器所采纳,其中包括Windows版的GCC。这种扩展在函数声明前使用__declspec属性。如果是遵从C命名规范(convention)的外部名字,它们必须在C++代码中声明为extern "C"以避免它们使用C++命名规范。

除了使用__declspec属性定义输入输出函数之外,它们也可以列在项目DEF文件的IMPORT或者EXPORTS部分。DEF文件由链接器而不是编译器进行处理,这样它就不是C++特有的。

DLL的编译将生成DLLLIB两个文件。LIB文件是在编译时用来链接DLL用的;它对于运行时链接不是必需的。除非你的DLL是一个COM服务器,DLL必须放在PATH环境变量、缺省系统路经或者是使用它的程序所在路径三个的一个之内。COM服务器DLL使用regsvr32.exe注册,它将DLL的路径和全局唯一身份(GUID)记录在注册表中。应用程序能够通过在注册表中查找GUID、找到它的路径从而使用这个DLL。

编程实例[编辑]

创建DLL输出函数[编辑]

下面的例子展示了与特定语言相关的从DLL输出符号表的方法。

Delphi

 library Example;
 
 // Function that adds two numbers
 function AddNumbers(a, b: Double): Double; cdecl;
 begin
     AddNumbers := a + b
 end;
 
 // Export this function
 exports
     AddNumbers;
 
 // DLL initialization code: no special handling needed
 begin
 end.

C 或 C++

 #include <windows.h>
 
 // Export this function
 extern "C" __declspec(dllexport) double AddNumbers(double a, double b);
 
 // DLL initialization function
 BOOL APIENTRY DllMain(HANDLE hModule, [[DWORD]] dwReason, LPVOID lpReserved)
 {
 	return TRUE;
 }
 
 // Function that adds two numbers
 double AddNumbers(double a, double b)
 {
 	return a + b;
 }

使用DLL输入[编辑]

下面的例子展示了与特定语言相关的如何在编译时链接DLL输入符号表的方法。

Delphi
 program Example;
 {$APPTYPE CONSOLE}
 
 // Import function that adds two numbers
 function AddNumbers(a, b: Double): Double; cdecl; external 'Example.dll';
 
 var result: Double;
 begin
 result := AddNumbers(1, 2);
 Writeln('The result was: ', result)
 end.

C 或 C++

 #include <windows.h>
 #include <stdio.h>
 
 // Import function that adds two numbers
 extern "C" __declspec(dllimport) double AddNumbers(double a, double b);
 
 int main(int argc, char **argv)
 {
 	double result = AddNumbers(1, 2);
 	printf("The result was: %f\n", result);
 	return 0;
 }

运行时使用显式调用[编辑]

下面的例子展示了如何使用不同语言特有的WIN32 API绑定进行运行时的调用和链接。

Microsoft Visual Basic

Option Explicit
Declare Function AddNumbers Lib "Example.dll" (ByVal a As Double, ByVal b As Double) As Double

Sub Main()
    Dim Result As Double
    Result = AddNumbers(1, 2)
    Debug.Print "The result was: " & Result
End Sub

C 或 C++

#include <windows.h>
#include <stdio.h>

// DLL function signature
typedef double (*importFunction)(double, double);

int main(int argc, char **argv)
{
    importFunction addNumbers;
    double result;

    // Load DLL file
    HINSTANCE hinstLib = LoadLibrary("Example.dll");
    if (hinstLib == NULL) {
        printf("ERROR: unable to load DLL\n");
        return 1;
    }

    // Get function pointer
    addNumbers = (importFunction)GetProcAddress(hinstLib, "AddNumbers");
    if (addNumbers == NULL) {
        printf("ERROR: unable to find DLL function\n");
        return 1;
    }

    // Call function.
    result = addNumbers(1, 2);

    // Unload DLL file
    FreeLibrary(hinstLib);

    // Display result
    printf("The result was: %f\n", result);

    return 0;
}

组件对象模型[编辑]

组件对象模型(COM)将DLL概念扩充到了面向对象编程。对象能够从另外一个进程调用或者在另外一台机器上运行。COM对象有一个唯一的GUID并且能够实现强大的后台以简化如Visual Basic和ASP这样的GUI前台应用。它们也可以使用脚本语言编程。COM对象的创建和使用比DLL更为复杂。

参见[编辑]

外部链接[编辑]

参考文献[编辑]

  • Hart, Johnson. Windows System Programming Third Edition. Addison-Wesley, 2005. ISBN 0-321-25619-0
  • Rector, Brent et al. Win32 Programming. Addison-Wesley Developers Press, 1997. ISBN 0-201-63492-9.