什么是 DLL

本文介绍动态链接库 (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 时,无需再次生成或安装整个程序即可应用更新。

以下列表描述了在Windows操作系统中作为 DLL 实现的一些文件:

  • 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 Windows及更高版本中引入的更改Windows操作系统,以帮助最大程度地减少依赖项问题:

  • Windows文件保护

    在Windows文件保护中,操作系统会阻止系统 DLL 被未经授权的代理更新或删除。 当程序安装尝试删除或更新定义为系统 DLL 的 DLL 时,Windows文件保护将查找有效的数字签名。

  • 专用 DLL

    通过专用 DLL,可以将程序与对共享 DLL 所做的更改隔离开来。 专用 DLL 使用特定于版本的信息或空 .local 文件来强制执行程序使用的 DLL 版本。 若要使用专用 DLL,请在程序根文件夹中找到 DLL。 然后,对于新程序,请将特定于版本的信息添加到 DLL。 对于旧程序,请使用空 .local 文件。 每个方法都告知操作系统使用位于程序根文件夹中的专用 DLL。

DLL 故障排除工具

有多种工具可用于帮助排查 DLL 问题。 以下工具是其中的一些工具。

依赖项 Walker

Dependency Walker 工具可以递归扫描程序使用的所有依赖 DLL。 在 Dependency Walker 中打开程序时,Dependency Walker 会执行以下检查:

  • 依赖项 Walker 检查是否缺少 DLL。
  • 依赖项 Walker 检查无效的程序文件或 DLL。
  • 依赖项 Walker 检查导入函数和导出函数是否匹配。
  • 依赖项 Walker 检查循环依赖项错误。
  • 依赖项 Walker 检查无效的模块,因为模块适用于不同的操作系统。

通过使用 Dependency Walker,可以记录程序使用的所有 DLL。 它可能有助于预防和纠正将来可能发生的 DLL 问题。 安装 Visual Studio 6.0 时,Dependency Walker 位于以下目录中:

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 函数位置所需的信息。

运行时动态链接

在运行时动态链接中,应用程序调用函数 LoadLibraryLoadLibraryEx 函数以在运行时加载 DLL。 成功加载 DLL 后,可以使用该 GetProcAddress 函数获取要调用的导出 DLL 函数的地址。 使用运行时动态链接时,不需要导入库文件。

以下列表介绍了何时使用加载时动态链接以及何时使用运行时动态链接的应用程序条件:

  • 启动性能

    如果应用程序的初始启动性能很重要,则应使用运行时动态链接。

  • 易用性

    在加载时动态链接中,导出的 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 函数添加函数关键字,也可以创建模块定义 (.def) 文件,其中列出了导出的 DLL 函数。

若要使用函数关键字,必须使用以下关键字声明要导出的每个函数:
__declspec(dllexport)

若要在应用程序中使用导出的 DLL 函数,必须使用以下关键字声明要导入的每个函数: __declspec(dllimport)

通常,你将使用一个具有定义语句和语 ifdef 句的标头文件来分隔 export 语句和 import 语句。

还可以使用模块定义文件声明导出的 DLL 函数。 使用模块定义文件时,无需将函数关键字添加到导出的 DLL 函数。 在模块定义文件中 LIBRARY ,声明 DLL 的语句和 EXPORTS 语句。 下面的代码是定义文件的示例。

// SampleDLL.def
//
LIBRARY "sampleDLL"
EXPORTS HelloWorld

示例 DLL 和应用程序

在 Visual C++ 6.0 中,可以通过选择 Win32 Dynamic-Link库 项目类型或 MFC AppWizard (dll) 项目类型来创建 DLL。

以下代码是使用 Win32 Dynamic-Link库 项目类型在 Visual C++ 中创建的 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

下面的代码是 一个 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 导入库。

在运行时动态链接中,使用类似于以下代码的代码来调用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:

  1. 应用程序文件夹

  2. 当前文件夹

  3. Windows系统文件夹

    备注

    GetSystemDirectory 函数返回Windows系统文件夹的路径。

  4. Windows文件夹

    备注

    GetWindowsDirectory 函数返回Windows文件夹的路径。

.NET Framework程序集

随着 .NET 和.NET Framework的引入,大多数与 DLL 相关联的问题都已通过使用程序集来消除。 程序集是在 .NET 公共语言运行时 (CLR) 的控制下运行的一个逻辑功能单元。 程序集以.dll文件或.exe文件的身份存在。 但是,在内部,程序集不同于 Microsoft Win32 DLL。

程序集文件包含程序集清单、类型元数据、Microsoft 中间语言 (MSIL) 代码和其他资源。 程序集清单包含程序集元数据,该元数据提供程序集自我描述所需的所有信息。 程序集清单中包含以下信息:

  • 程序集名称
  • 版本信息
  • 区域性信息
  • 强名称信息
  • 文件的程序集列表
  • 类型参考信息
  • 引用的和依赖的程序集信息

不能直接执行程序集中包含的 MSIL 代码。 而是通过 CLR 管理 MSIL 代码执行。 默认情况下,创建程序集时,程序集是应用程序专用的。 若要创建共享程序集,需要为程序集分配强名称,然后在全局程序集缓存中发布程序集。

下面的列表描述了与 Win32 DLL 功能相比程序集的一些功能:

  • 自我描述

    创建程序集时,CLR 运行程序集所需的所有信息都包含在程序集清单中。 程序集清单包含依赖程序集的列表。 因此,CLR 可以维护应用程序中使用的一组一致的程序集。 在 Win32 DLL 中,不能在使用共享 DLL 时在应用程序中使用的一组 DLL 之间保持一致性。

  • 版本控制

    在程序集清单中,版本信息由 CLR 记录和强制执行。 此外,版本策略允许你强制实施特定于版本的使用。 在 Win32 DLL 中,操作系统无法强制实施版本控制。 必须确保 DLL 向后兼容。

  • 并行部署

    程序集支持并行部署。 一个应用程序可以使用程序集的一个版本,另一个应用程序可以使用不同版本的程序集。 从 Windows 2000 开始,通过在应用程序文件夹中查找 DLL 来支持并行部署。 此外,Windows文件保护可防止系统 DLL 被未经授权的代理覆盖或替换。

  • 自我控制和隔离

    使用程序集开发的应用程序可以自包含,并与计算机上运行的其他应用程序隔离。 此功能可帮助你创建零影响安装。

  • 执行

    程序集在程序集清单中提供且由 CLR 控制的安全权限下运行。

  • 语言独立

    可以通过使用任何受支持的 .NET 语言来开发程序集。 例如,可以在 Microsoft Visual C# 中开发程序集,然后在 Visual Basic .NET 项目中使用该程序集。

References