更多信息
什么是 DLL?
动态链接库 (DLL) 是包含函数和数据的模块的集合。程序文件(如 .exe 文件或 .dll 文件)在运行时加载这些模块(亦即所需的模块映射到调用进程的地址空间)。下面两类函数定义了 DLL:
- 导出函数:这些函数由其他模块调用。
- 内部函数:这些函数仅从定义它们的 DLL 中调用。
DLL 还导出数据。不过,这些数据由相应的函数使用。
动态链接和静态链接
动态链接包括系统在加载或运行时查找导出的 DLL 函数的代码所需的信息。
在静态链接中,链接器将库函数的代码复制到调用 DLL 的每个模块。
可以通过下列方式调用 DLL 中的函数:
- 加载时动态链接:调用方模块执行显式调用以导出 DLL 函数。为 DLL 创建导入库,然后将 DLL 链接到应用程序。在加载应用程序时,导入库提供加载 DLL 和查找导出的 DLL 函数所需的信息。
- 运行时动态链接:在运行时加载 DLL 时,调用方模块使用 LoadLibrary 函数或 LoadLibraryEx 函数。调用方模块调用 GetProcAddress 函数以获取导出的 DLL 函数的地址。
在链接时,Windows 搜索预安装的一组 DLL,例如性能库 (Kernel32.dll) 和安全库 (User32.dll)。然后,Windows 按以下顺序搜索 DLL:
- 当前进程的可执行程序所在的目录。
- 当前目录。
- Windows 系统目录。(GetSystemDirectory 函数获取 Windows 系统目录的路径。)
- Windows 目录。(GetWindowsDirectory 函数获取 Windows 目录的路径。)
- PATH 环境变量中列出的目录。
注意:LIBPATH 环境变量不用于搜索。
返回页首
DLL 和静态库
- DLL 可节省内存并减少交换。 通过在内存中共享 DLL 的单个副本,多个进程可以同时使用一个 DLL。相比之下,对于使用静态链接库构建的每一个应用程序,Windows 都要在内存中为其加载库代码的一个副本。
- DLL 可节省磁盘空间。 多个应用程序可以共享磁盘上的一个 DLL 副本。相比之下,使用静态链接库构建的每一个应用程序都需要让链接到程序文件映像的库代码作为一个单独的专用副本。
- DLL 可节省时间。 更改 DLL 中的函数时,只要函数的参数和返回值不变,就不必重新编译或重新链接使用这些函数的应用程序。但是,如果您使用静态链接的对象代码,则在更改函数后必须重新链接应用程序。
- DLL 可以共享函数。 在 Win32 中,DLL 可以共享函数。默认情况下,数据对于每个进程来说是独立的。但是,静态库包含针对每一个进程的单独的数据副本和函数。
返回页首
DLL 入口点
DLL 有一个特殊的入口点(
DllMain 函数),它在附加和分离进程和线程时运行。此行为允许根据需要创建和销毁数据结构。文件扩展名为
.ocx、
.cpl 和
.drv 的文件类型也是 DLL,尽管文件扩展名已改变。在 Windows 2. x 和 Windows 3. x 中,每个 DLL 都只有一个数据段实例,而不管有多少应用程序。在 Windows 32 中,可以将 DLL 标记为共享以导致相同的行为。但是,每个进程的默认设置是拥有 DLL 数据的专用副本。
您可以通过创建 DLL 实现以下目的:
- 将程序划分为可按需加载的单独模块。
- 存储特定于语言或特定于区域的资源。
- 使您自己的应用程序能够使用核心代码库。
- 生成进程内 COM 对象或 ActiveX 控件 (OCX)。
- 将 OLE 对象用作进程内 DLL。这一用法可改进 OLE 链接的性能。
- 使用控制面板扩展或使用某些类型的驱动程序。
要生成 DLL,请使用
DllMain 函数(而不是程序文件)替换
WinMain 函数。
要在 Win16 中导出函数,请将
FAR EXPORT 添加到所有导出的 DLL 函数(在 Win32 中不要求这样做)。许多 32 位编译器提供了函数声明符,例如
__declspec(dllexport) 和
__declspec(dllimport)。这些函数声明符出现在函数声明之前以代替调用。不过,定义必须仅指定
dllexport 属性。
返回页首
DLL 问题和故障排除工具
初始化问题
在 DLL 初始化过程中,您可能会遇到下列一个或多个问题:
- 如果计划服务的服务帐户密码与域用户管理器帐户的密码不同,则您在计划服务时可能会收到以下初始化错误信息:
Initialization of dynamic link library c:/winnt/system32/KERNEL32.dll failed.Process is terminating abnormally
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 250265 使用 Cmd.exe 运行 AT 计划命令时出现 Kernel32.dll 动态链接库初始化错误
- 如果不断加载和卸载 Crypt32.dll,则在加载 Crypt32.dll 时将分配传输层安全性 (TLS),但在卸载 Crypt32.dll 时却不释放它。此行为可能导致一般性保护错误 (GPF) 错误,并且您可能会收到以下错误信息:
Initialization of the dynamic link library C:/WINNT40/System32/CRYPT32.dll failed.The process is terminating abnormally.
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 212825 SMS:一般性保护错误:Initialization of CRYPT32.DLL Failed(CRYPT32.DLL 初始化失败)
- 如果您将计算机升级到 McAfee VirusScan 6.0,则可能会在启动 Outlook 2000 后的三分钟内收到以下错误信息:
Outlook.exe. DLL initialization failed.Initialization of c:/winnt/system32/compstui.dll. Process is terminating abnormally.
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 310413 OL2000:升级到 McAfee VirusScan 6.0 后 Outlook 2000 发生 DLL 初始化错误
- 如果您将 Windows NT Mail 或 MSMail32.exe 设置为按计划启动,计划服务可能被记录为系统以外的其他内容,而且您可能会收到以下错误信息:
Initialization of the Dynamic Link Library OLECLI32.dll failed Abnormally
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 133476 Windows NT 邮件错误信息:Initialization of the Dynamic Link...(动态链接的初始化...)
- 如果远程访问服务 (RAS) 文件或调制解调器文件丢失或损坏,并且您试图打开控制面板,则可能会收到以下错误信息:
Explorer.exe - DLL Initialization Failed Initialization of the dynamic link library E:/WINNT/System32/RASSCRPT.dll failed.The process is terminating abnormally.
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 184443 错误信息:Explorer.exe - DLL Initialization Failed...(Explorer.exe — DLL 初始化失败...)
- 如果系统内存不足,无法为正在启动的服务创建新的桌面堆栈,您可能会收到以下错误信息:
ServiceName - DLL initialization failure Initialization of the dynamic link library c:/windows/system32/user32.dll failed.The process is terminating abnormally.
有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 142676 解决 User32.dll 初始化失败错误
返回页首
依赖性问题
如果您没有等到所有系统服务开始运行就启动您的服务,可能会遇到依赖性问题。例如,服务依赖于其他服务或驱动程序。这些服务或驱动程序又转而依赖于另外一些服务或驱动程序。更确切地说,RasApi32.dll 隐式依赖于 RAS 服务。使用 Rasapi32.dll 上的
LoadLibrary 的一个 Windows NT 服务使该服务依赖于 RAS 服务。
在您对项目进行更改时,定期通过“项目”菜单扫描所有依赖项以确保依赖性列表的准确性。如果您在执行依赖性扫描之前未保存对项目中文件所做的更改,新的依赖项不会显示在列表中。
返回页首
性能问题
如果加载应用程序的同时加载隐式链接到该应用程序的 DLL,则会出现性能问题。例如,当运行 Windows 2000 的计算机在内存中加载 Winmm.dll 文件时,可能会出现延迟。此延迟可能会在不同的时间发生,具体取决于 Winmm.dll 文件是如何加载的:
- 如果程序使用导入库来加载 Winmm.dll 文件,则 Winmm.dll 在程序启动时加载。在这种情况下,可能会在启动程序时出现延迟。因此,如果使用批处理文件多次启动此程序,性能会显著下降。
- 如果程序使用 LoadLibrary 加载 Winmm.dll 文件,则 Winmm.dll 仅在程序需要时才加载。例如,如果在您打开菜单时 Explorer.exe 播放声音,则在第一次播放声音时加载 Winmm.dll 文件。在这种情况下,可能会在程序加载 Winmm.dll 文件时出现延迟。
要提高性能,请在加载时使用优化技巧。另外,可将 DLL 分为两个 DLL:
- 将调用应用程序在加载后立刻需要的所有函数放入一个 DLL,然后将该调用应用程序隐式链接到此 DLL。
- 将调用应用程序不立即需要的函数放入另一个 DLL,然后将该应用程序显式链接到此 DLL。
返回页首
故障排除工具
支持使用下列工具进行故障排除:
- Dependency Walker Dependency Walker 不仅仅是一个故障排除实用工具。Dependency Walker 提供了许多关于某一特定应用程序的模块布局的宝贵信息。此实用工具还提供关于每个模块的详细信息。有关更多信息,请参阅本文的“DLL 依赖性”部分。
- DLL Universal Problem Solver 工具出现 DLL 文件版本冲突问题(也称为“DLL Hell”问题)时,可以使用 DLL Universal Problem Solver (DUPS) 工具查找这些问题并进行修复。DUPS 程序包是一个实用工具集,使用它可以跟踪和比较多台基于 Windows 的计算机上的 DLL 版本。有关更多信息,请访问下面的 Microsoft Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsetup/html/dlldanger1.asp
- RegSvr32 使用 Regsvr32 工具 (Regsvr32.exe) 可注册或注销 OLE 控件,例如可自行注册的 DLL 控制文件或 ActiveX 控件 (OCX) 文件。为排查 Windows、Microsoft Internet Explorer 或其他程序中的故障,您可能必须注册或注销这些文件。 有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
249873 Regsvr32 用法和错误消息的说明
- OLE/COM 对象查看器使用 OLE/COM 对象查看器,可通过读取类型库来查看控件(DLL 或 OCX 文件)的接口。有关更多信息,请访问下面的 Microsoft Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefUsingOLECOMObjectViewerToViewControlsInterfaces.asp
- .NET Framework 工具 Microsoft .NET Framework SDK 工具旨在使您创建、部署和管理使用 .NET Framework 的应用程序和组件的过程更轻松。.NET Framework 提供四组工具:配置和部署工具、调试工具、安全工具和常规工具。有关更多信息,请访问下面的 Microsoft Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpconnetframeworktools.asp
返回页首
DLL 依赖性
通过使用 Dependency Walker,您可以查看应用程序对 DLL 的依赖情况。例如,Depends.exe 包含在 Visual C++ .NET 中。Depends.exe 安装在 /Microsoft Visual Studio .NET 2003/Common7/Tools/Bin 文件夹中。仅当您在 Visual C++ .NET 自定义安装的 Visual C++ 工具类别中选择了 Win32 Platform SDK 工具时,才可以安装 Depends.exe 可执行文件。
通过使用 Depends.exe 或 DUMPBIN 实用工具并使用
/DEPENDENTS 选项,可以查看静态链接到您的应用程序的 DLL 列表和延迟加载的 DLL 的列表。要查看动态加载了哪些 DLL(如 ActiveX 控件),请使用 Depends.exe 的信息摘要功能。有关更多信息,请访问下面的 Microsoft Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vccondeterminingwhichdllstoredistribute.asp
返回页首
Win32 与 .NET 程序集的比较
在 .NET Framework 中,程序集是一个逻辑 EXE 或 DLL。下面是程序集与 Win32 DLL 相比较二者之间的不同之处:
- 这些 EXE 或 DLL 文件中的代码是由语言编译器生成的。此代码不是 x86 计算机代码或专用于任何特定 CPU 操作系统的计算机代码。该代码是用 Microsoft 中间语言 (MSIL) 生成的。MSIL 可以用汇编语言编写。Microsoft 提供了 MSIL 汇编程序 (ILAsm.exe) 和 MSIL 反汇编程序 (ILDasm.exe)。
- 此 DLL 或 EXE 文件包含编译器产生的元数据。公共语言运行库使用此元数据查找和加载文件中的类类型,确定对象实例在内存中的布局,解析方法调用和字段引用,将 MSIL 转换为本机代码,执行安全设置。
- 您生成的组件不仅仅是 EXE 文件或 DLL 文件。它们是重新使用和部署的基本单位,是 .NET 中的一个程序集。您可以生成单文件程序集或多文件程序集。在单文件程序集中,程序集的文件扩展名是 .exe 或 .dll。
多文件程序集是这样一种思想:通过将一个可重用组件的逻辑概念和物理概念分离开来,将代码划分到多个文件中以满足您的需求。不过,您仍然可以保留集合作为版本控制和部署的一个单位。例如,如果您使用多文件程序集,则可以使用增量和按需下载。
- 通过使用程序集,开发人员和管理员能够表示程序的各组件之间严格的版本依赖性。由于各组件是自描述的,所以您可以创建无影响安装。
在安装新应用程序的组件时,它们可能覆盖早期的应用程序的组件。如果发生此覆盖,早期的应用程序可能会运行不正常或停止运行。.NET Framework 体系结构让应用程序的各组件保持独立,这样应用程序始终能够加载正确的组件。因此,在安装后能按预期运行的应用程序将继续运行。(此功能解决了“DLL Hell”问题。)
- 程序集是 .NET 安全系统的一部分。程序集是申请和授予权限的单位。为了正确地在 .NET Framework 中强制实施版本控制、部署和安全功能,程序集起到了解析范围的作用,用于解析对类型的引用。
清单 中描述了程序集的各个组件。清单是一个数据块,它列出程序集的文件,并控制着向程序集之外公开哪些类型和资源。例如,某些类型可能保留为仅在程序集内使用。其他类型可能能够导出到程序集的使用者。.NET Framework 按照在程序集清单中表达的规则将类型和资源解析为程序集中的实现文件和绑定到从属程序集。
虽然 .NET Framework 很大而且范围很广,但仍然存在可使用 Win32 API 执行,而使用 .NET Framework 则无法执行的一些功能。例如,您可以使用 Win32 API
MessageBeep 和
Beep 在应用程序中包含生成噪音的功能。
返回页首
.NET 连接的客户端应用程序使用 Win32 DLL 导出 (PInvoke)
要通过 .NET 连接的应用程序使用 Win32 DLL 例程,请使用“平台调用服务”机制(也叫平台调用或
PInvoke)。此服务通过在
System.Runtime.InteropServices 命名空间中定义的一个自定义属性 (
DllImportAttribute) 运行。该属性允许实现 DLL 的名称(和其他必需信息)与一个过程或函数声明关联。通过此关联,您可以调用 DLL 例程。组件对象模型 (COM) Interop 支持使用 Interop 封送拆收器自动在托管 .NET 环境和非托管 Win32 环境之间封送拆收参数和返回值。
要使用导出的 DLL 函数,请按照下列步骤操作:
- 标识函数名称和导出该函数的 DLL 的名称。例如,通过指定 User32.dll 中的 MessageBox 函数,您可以标识函数 (MessageBox) 和函数的位置(User32.dll、User32 或 user32)。
- 创建一个类以包装 DLL 函数。在类中为要调用的每个 DLL 函数定义一个静态方法。例如,您可以调用 User32.dll 中的 MessageBox 函数,如下例所示:
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
public static extern int MessageBox(int hWnd, String text,
String caption, uint type);
- 使用 DllImportAttribute 属性标识 DLL 和函数。使用外部“C”标记包装方法或函数。
- 通过传递在托管代码中定义的类的成员,调用托管类中的方法。 例如,将 MySystemTime 类的成员(这些成员是按顺序定义的)传递到 User32.dll 文件中的 GetSystemTime 方法,如下例中所示:
void GetSystemTime(SYSTEMTIME* SystemTime);
class Win32API {
[DllImport("Kernel32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
有关更多信息,请访问下面的 Microsoft Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconsumingunmanageddllfunctions.asp
返回页首
Win32 客户端应用程序使用 .NET 程序集导出(反向 PInvoke)
要在 Win32 应用程序中使用 .NET 程序集,请使用“反向平台调用服务”机制。 此服务使用的机制与
PInvoke 使用的机制反向。
通过使用此机制,.NET 中的公共语言运行库可以向外部 (Win32) 世界公开任何 .NET 方法。此机制执行下列操作:
- 将 .NET 程序集反汇编为对应的中间语言 (IL) 源代码和元数据。
- 重新汇编对等的 .NET 程序集中的 IL 代码、元数据和资源。
详细信息
要对 .NET 程序集进行反汇编,您可以使用 Framework SDK 中包含的 .NET Framework IL 反汇编程序 (Ildasm.exe)。 例如,MyAssembly.dll 包含下列独立例程,即 HelloWorld 和 HelloMyWorld:
public static void HelloWorld()
{
Console.WriteLine("Hello World!");
}
public static void HelloMyWorld()
{
Console.WriteLine("Hello My World!");
}
要对该程序集进行反汇编,请使用以下命令行:
ildasm MyAssembly.dll /linenum /out:MyAssembly.il
此命令将生成命名的 IL 文件。它还在 MyAssembly.res 文件中存储任何非托管资源,在具有程序集元数据中指定的名称的文件中存储任何托管资源。如果 PDB 文件中包含调试信息,则
/linenum 选项使 IL 文件包括对原始源代码行的引用。
返回页首
从程序集公开方法
要从程序集公开方法,必须对程序集清单进行某些更改。该清单位于 IL 文件顶部的类声明之前:
.module MyAssembly.dll
// MVID: {140B1958-FC28-4802-80E3-74C0F2015CDC}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
要进行更改,请执行下列操作:
- 要定义一个 v-table 修正并使之包含的插槽与要导出的方法一样多(上例中有两个方法),请按如下所示更改清单:
module MyAssembly.dll
.module MyAssembly.dll
// MVID: {140B1958-FC28-4802-80E3-74C0F2015CDC}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
.data VT_01 = int32[2]
.vtfixup [2] int32 fromunmanaged at VT_01
- .corflags 指令设置运行库头文件标志。默认情况下,.corflags 指令指定值 1。 此值等于 COMIMAGE_FLAGS_ILONLY 标志(在 .NET Framework SDK 中的 CorHdr.h 包含文件中定义)。 设置此标志后,Windows XP 加载程序会忽略该程序集文件的主要部分,而且不进行修正。 在您尝试使用程序集导出时,这会导致致命错误。 所以,如果您希望程序集在 Windows XP 上运行,必须指定 COMIMAGE_FLAGS_32BITREQUIRED 标志(此值为 2):
module MyAssembly.dll
.module MyAssembly.dll
// MVID: {140B1958-FC28-4802-80E3-74C0F2015CDC}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000002.data VT_01 = int32[2]
.vtfixup [2] int32 fromunmanaged at VT_01
这些方法的 IL 表示当前显示如下:
.method public hidebysig static void
HelloWorld() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.line 18:4
IL_0000: ldstr "Hello World!"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.line 19:3
IL_000a: ret
} // end of method Class1::HelloWorld
.method public hidebysig static void
HelloMyWorld() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.line 23:4
IL_0000: ldstr "Hello My World!"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.line 24:3
IL_000a: ret
} // end of method Class1::HelloMyWorld
要让这些方法作为非托管导出,请对它们进行如下更改:
.method public hidebysig static void
HelloWorld() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.line 18:4
.vtentry 1:1
.export [1] as HelloWorld
IL_0000: ldstr "Hello World!"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.line 19:3
IL_000a: ret
} // end of method Class1::HelloWorld
.method public hidebysig static void
HelloMyWorld() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.line 23:4
.vtentry 1:2
.export [2] as HelloMyWorld
IL_0000: ldstr "Hello My World!"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
.line 24:3
IL_000a: ret
} // end of method Class1::HelloMyWorld
每个方法需要一个 .vtentry 指令以将该方法链接到 v-table 修正(粗体显示指定的插槽号),还需要一个 .export 指令以指定导出的名称。
要将代码重新汇编为 .NET 程序集,请使用 .NET Framework 中包含的 IL 汇编程序 (Ilasm.exe)。使用以下命令行:
C:/Temp/MyAssembly/bin/Debug>ilasm /dll MyAssembly.il /out:MyAssembly_new.dll /r
es:MyAssembly.res
Microsoft (R) .NET Framework IL Assembler. Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
Assembling 'MyAssembly.il' , no listing file, to DLL --> 'MyAssembly_new.dll'
Source file is ANSI
Assembled method Class1::Main
Assembled method Class1::HelloWorld
Assembled method Class1::HelloMyWorld
Assembled method Class1::.ctor
Creating PE file
Emitting members:
Global
Class 1 Methods: 4;
Resolving member refs: 1 -> 1 defs, 0 refs
Writing PE file
Operation completed successfully
使用反向 PInvoke 时可能遇到的主要困难是维护问题。如果要通过创造性往返修改编译器生成的程序集,请重新手动更新程序集以导出例程。
返回页首