本文介绍动态链接库 (DLL) ,以及使用 DLL 时可能会出现的各种问题。 它还介绍了在开发自己的 DLL 时应考虑的一些高级问题。
适用于: Windows 10 - 所有版本
原始 KB 编号: 815065

摘要

本文介绍动态链接方法、DLL 依赖关系、DLL 入口点、导出 DLL 函数和 DLL 疑难解答工具,以描述 DLL 是什么。
本文最后对 DLL 与Microsoft .NET Framework进行高级比较。
对于Windows操作系统,操作系统的很多功能都由 DLL 提供。 此外,当您在这些操作系统之一Windows程序时,该程序的很多功能可能由 DLL 提供。 例如,某些程序可能包含许多不同的模块,并且程序的每个模块包含在 DLL 中并分发。
使用 DLL 有助于促进代码的模块化、代码重用、有效的内存使用并减少磁盘空间。 因此,操作系统和程序加载速度更快,运行速度更快,并且占用了更少的磁盘空间。
当程序使用 DLL 时,称为依赖关系的问题可能会导致程序不运行。 当程序使用 DLL 时,将创建依赖项。 如果另一个程序覆盖并中断此依赖关系,则原始程序可能无法成功运行。
通过引入 .NET Framework,大多数依赖关系问题已通过使用程序集消除。

更多信息

DLL 是一个库,其中包含可同时由多个程序使用的代码和数据。 例如,在Windows中,Comdlg32 DLL 执行与对话框相关的常见功能。 每个程序都可以使用此 DLL 中包含的功能来实现"打开"对话框。 它有助于促进代码重用和高效内存使用。
通过使用 DLL,程序可以模块化为单独的组件。 例如,会计程序可以按模块出售。 如果安装了主程序,则每个模块都可以运行时加载到主程序中。 由于模块是独立的,因此程序的加载时间会更快。 并且仅在请求该功能时加载模块。
此外,更新更易于应用于每个模块,而不会影响程序的其他部分。 例如,你可能有一个工资单计划,并且税率会每年更改。 当这些更改被隔离到 DLL 中时,无需再次构建或安装整个程序,即可应用更新。
以下列表介绍了在操作系统中作为 DLL 实现的一Windows文件:
ActiveX控件 (.ocx) 文件
一个ActiveX示例是一个日历控件,它允许您从日历中选择日期。
控制面板 (.cpl) 文件
例如,.cpl位于"控制面板"中的项目。 每个项目都是一个专门的 DLL。
设备驱动程序 (.drv) 文件
设备驱动程序的一个示例是控制打印到打印机的打印机驱动程序。

DLL 优势

以下列表描述了当程序使用 DLL 时提供的一些优势:
使用更少的资源
当多个程序使用相同的函数库时,DLL 可以减少在磁盘和物理内存中加载的代码的重复。 它不仅会大大影响前台运行的程序的性能,还会对运行在 Windows 操作系统上的其他程序的性能产生很大影响。
提升模块化体系结构
DLL 有助于促进开发模块化程序。 它可以帮助你开发需要多种语言版本的大型程序或需要模块化体系结构的程序。 模块化程序的一个示例是一个会计程序,该程序具有许多可在运行时动态加载的模块。
简化部署和安装
当DLL 中的函数需要更新或修复时,DLL 的部署和安装不需要程序与 DLL 重新链接。 此外,如果多个程序使用相同的 DLL,则多个程序都将从更新或修补程序中获益。 使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。

DLL 依赖项

当程序或 DLL 在另一个 DLL 中使用 DLL 函数时,将创建依赖项。 程序不再为独立程序,如果依赖关系断开,程序可能会遇到问题。 例如,如果发生以下操作之一,该程序可能无法运行:
从属 DLL 将升级到新版本。从属 DLL 是固定的。依赖 DLL 将被早期版本覆盖。从计算机中删除依赖 DLL。
这些操作称为 DLL 冲突。 如果未强制向后兼容,则该程序可能无法成功运行。
以下列表介绍了 2000 年2Windows及更高版本中引入的更改Windows以帮助最大程度地减少依赖项问题:
Windows文件保护
在Windows保护中,操作系统可防止未经授权的代理更新或删除系统 DLL。 当程序安装尝试删除或更新定义为系统 DLL 的DLL 时,Windows文件保护将查找有效的数字签名。
私有 DLL
通过专用 DLL,可以将程序与对共享 DLL 所做的更改隔离开。 私有 DLL 使用特定于版本的信息或空文件强制执行程序.local使用的 DLL 版本。 若要使用专用 DLL,请在程序根文件夹中找到 DLL。 然后,对于新程序,将特定于版本的信息添加到 DLL。 对于旧程序,请使用空.local文件。 每个方法都指示操作系统使用位于程序根文件夹中的私有 DLL。

DLL 疑难解答工具

可以使用多种工具来帮助您排查 DLL 问题。 以下工具是其中一些工具。

依赖者

依赖项规划器工具可以递归扫描程序使用的所有依赖 DLL。 当你在依赖关系规划程序中打开程序时,依赖关系管理程序将执行以下检查:
依赖关系中心检查缺少的 DLL。依赖项规划程序检查程序文件或 DLL 是否无效。依赖项检查导入函数和导出函数是否匹配。依赖项检查循环依赖错误。依赖关系开发程序会检查无效,因为这些模块适用于不同的操作系统。
通过使用依赖关系管理程序,你可以记录程序使用的所有 DLL。 它可以帮助防止和更正将来可能会发生的 DLL 问题。 安装 6.0 版时,依赖项 Visual Studio位于以下目录中:
drive\Program Files\Microsoft Visual Studio\Common\Tools

DLL 通用问题规划求解

DLL 通用问题规划 (DUPS) 工具用于审核、比较、记录并显示 DLL 信息。 以下列表介绍了创建 DUPS 工具的实用程序:
Dlister.exe
此实用工具枚举计算机上的所有 DLL,将信息记录到文本文件或数据库文件中。
Dcomp.exe
此实用工具比较两个文本文件中列出的 DLL,并生成第三个包含这些差异的文本文件。
Dtxt2DB.exe
此实用工具将通过使用 Dlister.exe 和Dcomp.exe 实用程序创建的文本文件加载到 dllHell 数据库中。
DlgDtxt2DB.exe
此实用工具提供图形用户界面 (GUI) 版本的 Dtxt2DB.exe 实用工具。

DLL 帮助数据库

DLL 帮助数据库可帮助您找到由 Microsoft 软件产品安装的 DLL 的特定版本。

DLL 开发

本节介绍在开发自己的 DLL 时应考虑的问题和要求。

DLL 的类型

在应用程序中加载 DLL 时,两种链接方法让你可以调用导出的 DLL 函数。 链接的两个方法是加载时动态链接和运行时动态链接。
加载时动态链接
在加载时动态链接中,应用程序显式调用导出的 DLL 函数,如本地函数。 若要使用加载时动态链接,在编译和链接应用程序时,请提供标头 (.h) 文件和导入库 (.lib) 文件。 当您这样做时,链接器会向系统提供加载 DLL 和在加载时解析导出的 DLL 函数位置所需的信息。
运行时动态链接
在运行时动态链接中,应用程序会调用LoadLibrary函数LoadLibraryEx或 函数以运行时加载 DLL。 成功加载 DLL 后,使用 函数获取要调用的导出GetProcAddressDLL 函数的地址。 使用运行时动态链接时,不需要导入库文件。
以下列表介绍了何时使用加载时动态链接以及何时使用运行时动态链接的应用程序条件:
启动性能
如果应用程序的初始启动性能很重要,则应该使用运行时动态链接。
易用性
在加载时动态链接中,导出的 DLL 函数与本地函数类似。 这便于调用这些函数。
应用程序逻辑
在运行时动态链接中,应用程序可以分支以按需要加载不同的模块。 开发多种语言版本时,这一点很重要。

DLL 入口点

创建 DLL 时,可以选择指定入口点函数。 当进程或线程将自己附加到 DLL 或将自己与 DLL 分离时,将调用入口点函数。 可以使用入口点函数初始化数据结构或破坏 DLL 所需的数据结构。 此外,如果应用程序是多线程的,可以使用线程本地存储 (TLS) 分配入口点函数中每个线程专用的内存。 以下代码是 DLL 入口点函数的一个示例。
BOOL APIENTRY DllMain( HANDLE hModule,// Handle to DLL module DWORD ul_reason_for_call,// Reason for calling function LPVOID lpReserved ) // Reserved { switch ( ul_reason_for_call ) { case DLL_PROCESS_ATTACHED: // A process is loading the DLL. break; case DLL_THREAD_ATTACHED: // A process is creating a new thread. break; case DLL_THREAD_DETACH: // A thread exits normally. break; case DLL_PROCESS_DETACH: // A process unloads the DLL. break; } return TRUE; }
当入口点函数返回 FALSE 值时,如果使用加载时动态链接,应用程序将不会启动。 如果使用运行时动态链接,将不会加载单个 DLL。
入口点函数只应执行简单的初始化任务,不应调用任何其他 DLL 加载或终止函数。 例如,在入口点函数中,不应直接或间接调用LoadLibrary函数LoadLibraryEx或 函数。 此外,当进程终止FreeLibrary时,不应调用 函数。
备注
在多线程应用程序中,确保对 DLL 全局数据的访问在线程安全 (同步) 以避免数据损坏。 为此,请使用 TLS 提供每个线程的唯一数据。

导出 DLL 函数

若要导出 DLL 函数,您可以将函数关键字添加到导出的 DLL 函数,或创建一个列出导出 DLL 函数的 (.def) 文件。
若要使用函数关键字,必须使用以下关键字声明要导出的每个函数:
__declspec(dllexport)
若要在应用程序中使用导出的 DLL 函数,您必须使用以下关键字声明要导入的每个函数:__declspec(dllimport)
通常,您将使用一个包含 define 语句和一个语句的头文件来分隔ifdef导出语句和import语句。
您还可以使用模块定义文件声明导出的 DLL 函数。 使用模块定义文件时,不需要将函数关键字添加到导出的 DLL 函数。 在模块定义文件中,声明 DLLLIBRARYEXPORTS的 语句和 语句。 以下代码是定义文件的示例。
// SampleDLL.def // LIBRARY "sampleDLL" EXPORTS HelloWorld

示例 DLL 和应用程序

在Visual C++ 6.0 中,可以通过选择Win32 Dynamic-Link Library项目类型或MFC AppWizard(dll) 创建 DLL。
以下代码是使用Win32Visual C++ 库项目类型在 Dynamic-Link 中创建的 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_DLL extern __declspec(dllexport) void HelloWorld(); #else extern __declspec(dllimport) void HelloWorld(); #endif #endif
以下代码是调用 SampleDLL DLL 中导出的 DLL 函数的Win32 应用程序项目的示例。
// 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 导入库。
在运行时动态链接中,使用类似于以下代码的代码来调用SampleDLL.dll DLL 函数。
... typedef VOID (*DLLPROC) (LPTSTR); ... HINSTANCE hinstDLL; DLLPROC HelloWorld; BOOL fFreeDLL; hinstDLL = LoadLibrary("sampleDLL.dll"); if (hinstDLL != NULL) { HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld"); if (HelloWorld != NULL) (HelloWorld); fFreeDLL = FreeLibrary(hinstDLL); } ...
编译和链接 SampleDLL 应用程序时,Windows操作系统将按此顺序在下列位置搜索 SampleDLL DLL:
应用程序文件夹
当前文件夹
系统Windows文件夹
备注
GetSystemDirectory 函数返回系统Windows的路径。
"Windows"文件夹
备注
GetWindowsDirectory 函数返回文件夹Windows路径。

程序集.NET Framework程序集

通过引入 .NET 和.NET Framework,使用程序集已消除与 DLL 关联的大多数问题。 程序集是在 .NET 公共语言运行时和 CLR (控制下运行的逻辑功能) 。 程序集在物理上作为.dll或作为文件.exe存在。 但是,内部程序集不同于 Microsoft Win32 DLL。