0%

Windows DLL 三种加载方式:隐式、显式、延迟加载到底差在哪

一个容易误读的讲法

读王端明《深入浅出 Windows API 程序设计》核心编程篇的 DLL 章,书里先讲显式加载的好处是”DLL 不存在,exe 也能跑起来”,接着讲延迟加载,又说它有”目录里没有 DLL 也能把程序运行起来”的优势。两段话放一起看容易犯迷糊:延迟加载写法上明明还是隐式的(链 .lib、直接调函数),怎么也有显式加载的好处?这是不是讲错了?

结论:书上没错,但要把两件事拆开看——写法上的链接方式和加载发生的时机。延迟加载是”写法上隐式、时机上独立”的第三种方式,不等于隐式加载。

三种方式对照

隐式加载 显式加载 延迟加载
写法 链接 .lib,直接调函数 LoadLibrary + GetProcAddress,手动拿函数指针 链接 .lib,直接调函数(加 /DELAYLOAD:dll名 链接选项)
DLL 何时被加载 进程启动时,加载器解析整个导入表 你调 LoadLibrary 那一刻 第一次调用该 DLL 的某个函数时
DLL 不存在,exe 能启动吗 不能,进程直接起不来(弹”找不到 xxx.dll”) 能,LoadLibrary 返回 NULL,自己决定怎么办 能
DLL 不存在且真的调用了它的函数 不会发生(启动就死了) 不会发生(拿不到指针,走自己的失败分支) 调用那一刻才失败:抛结构化异常,默认进程崩,可用失败钩子 __pfnDliFailureHook2 兜底

两段”优势”的真实关系

显式加载的优势是全程可控:DLL 不在程序照样跑,连调用失败都在你手里。

延迟加载只有这个优势的一半:DLL 不在程序能启动,它把失败从启动时刻推迟到调用时刻。程序跑的这条路径如果不碰这个 DLL,就白赚了启动速度和健壮性;真碰了而 DLL 不在,照样要处理异常。

所以延迟加载并没有推翻”显式加载更安全”的说法,它只是让隐式写法的代码也获得了”先跑起来”的能力。

代码长什么样

显式加载(完全自己控制):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <windows.h>
#include <cstdio>

typedef int (WINAPI *AddFunc)(int, int);

int main() {
HMODULE h = LoadLibraryW(L"MyMath.dll");
if (!h) {
printf("DLL 不存在, 走降级逻辑\n"); // 程序没死, 自己接住.
return 0;
}
AddFunc add = (AddFunc)GetProcAddress(h, "add");
if (add) {
printf("1 + 2 = %d\n", add(1, 2));
}
FreeLibrary(h);
}

延迟加载(代码和隐式一模一样,只改链接选项):

1
2
项目属性 → 链接器 → 输入 → 延迟加载的 DLL: MyMath.dll
或命令行: /DELAYLOAD:MyMath.dll
1
2
3
4
5
6
7
8
// 源码层面和隐式加载完全相同, 直接调, 不用 GetProcAddress.
__declspec(dllimport) int add(int, int);

int main() {
// 程序启动时 MyMath.dll 还没加载.
// 第一次调 add() 时系统才真正去加载它, 此刻 DLL 不在就抛异常.
return add(1, 2);
}

一句话记忆

  • 隐式:启动时结账,缺货直接不开门。
  • 显式:全程自助,自己去仓库取货,取不到自己想办法。
  • 延迟加载:先开门营业,用到哪件货才去取,取不到时当场出事(但留了钩子让你补救)。

延迟加载的典型用途

某个 DLL 只在少数功能路径上用到时最合适:打印功能、某些系统版本才存在的新 API、体积大的可选组件。推迟加载既加快启动速度,又让”可选组件缺失”不阻塞主功能。判断标准就一条:这条路径上 DLL 缺失是可以接受、需要降级的,还是致命的——前者用延迟加载很划算,后者还是老老实实隐式加载,启动时就暴露问题。