动态链接库

动态链接库
动态链接库

动态链接库(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的字体文档。

目录

[隐藏]

? 1 背景

? 2 特征

o 2.1 内存管理

o 2.2 符号解析和绑定

o 2.3 运行时显式链接

? 3 编译器和语言考虑

o 3.1 Delphi

o 3.2 微软Visual Basic

o 3.3 C和C++

? 4 编程实例

o 4.1 创建DLL输出函数

o 4.2 使用DLL输入

o 4.3 运行时使用显式调用

? 5 组件对象模型

? 6 参见

?7 外部链接

?8 参考文献

[编辑] 背景

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 注销此计数器。

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

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

运行时显式链接的过程在所有语言中都是相同的,因为它依赖于Windows API而不是语言结构。[编辑] 编译器和语言考虑

[编辑] Delphi

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

Delphi不需要LIB文档以从DLL中输入函数。为了链接一个DLL,在函数声明中使用关键词external。[编辑] 微软Visual Basic

在Visual Basic(VB)中只支持运行时链接;但是除了使用LoadLibrary和GetProcAddress这两个API函数之外,允许使用输入函数的声明。

通过声明输入DLL函数,如果找不到DLL文档VB将产生一个运行时的错误。开发人员能够抓住这个错误并且进行适当的处理。

[编辑] C和C++

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

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

DLL的编译将生成DLL和LIB两个文档。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

// 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

#include

// 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

#include

// 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更为复杂。

(注:文档可能无法思考全面,请浏览后下载,供参考。可复制、编制,期待你的好评与关注)

相关主题
相关文档
最新文档