一个容易误读的讲法
读王端明《深入浅出 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 |
|
延迟加载(代码和隐式一模一样,只改链接选项):
1 | 项目属性 → 链接器 → 输入 → 延迟加载的 DLL: MyMath.dll |
1 | // 源码层面和隐式加载完全相同, 直接调, 不用 GetProcAddress. |
一句话记忆
- 隐式:启动时结账,缺货直接不开门。
- 显式:全程自助,自己去仓库取货,取不到自己想办法。
- 延迟加载:先开门营业,用到哪件货才去取,取不到时当场出事(但留了钩子让你补救)。
延迟加载的典型用途
某个 DLL 只在少数功能路径上用到时最合适:打印功能、某些系统版本才存在的新 API、体积大的可选组件。推迟加载既加快启动速度,又让”可选组件缺失”不阻塞主功能。判断标准就一条:这条路径上 DLL 缺失是可以接受、需要降级的,还是致命的——前者用延迟加载很划算,后者还是老老实实隐式加载,启动时就暴露问题。