什么是 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 冲突。 如果未强制向后兼容,则该程序可能无法成功运行。

以下列表介绍了在 Windows 2000 和更高版本的 Windows 操作系统中引入的更改,以帮助最大程度地减少依赖项问题:

  • Windows 文件保护

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

  • 专用 DLL

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

DLL 疑难解答工具

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

依赖者

依赖关系处理工具可以递归扫描程序使用的所有从属 DLL。 When you open a program in Dependency Dependency Dependenc, Dependency则执行以下检查:

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

通过使用 Dependency管理程序,您可以记录程序使用的所有 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 后,使用该函数获取要调用的导出 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 函数,或创建一个列出导出的 DLL 函数的 (.def) 文件。

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

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

通常,您将使用一个包含 define 语句和一个语句的头文件来分隔导出语句和 ifdef import 语句。

您还可以使用模块定义文件声明导出的 DLL 函数。 使用模块定义文件时,不需要将函数关键字添加到导出的 DLL 函数。 在模块定义文件中,声明 DLL LIBRARY 的语句 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 代码无法直接执行。 相反,MSIL 代码执行通过 CLR 进行管理。 默认情况下,创建程序集时,程序集是应用程序的专用程序集。 若要创建共享程序集,需要为程序集分配一个强名称,然后在全局程序集缓存中发布该程序集。

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

  • 自我描述

    创建程序集时,CLR 运行程序集所需的全部信息都包含在程序集清单中。 程序集清单包含相关程序集的列表。 因此,CLR 可以保持应用程序中使用的一组一致的程序集。 在 Win32 DLL 中,在使用共享 DLL 时,无法在应用程序中使用的一组 DLL 之间保持一致性。

  • 版本控制

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

  • 并行部署

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

  • 自我包含和隔离

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

  • 执行

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

  • 独立于语言

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

参考