Windows 下 C++ 动态库生成的
.lib,通常不是“代码库”,而是“导入库(Import Library)”。它不包含 DLL 的实现代码,只包含让链接器把调用方和 DLL 绑起来的元数据与桩。
很多人把.lib当成“半个 DLL”,这是最根本的误解。
一、先分清:两种.lib完全不是一回事
Windows 里.lib只是扩展名,背后有两种东西:
类型 | 本质 | 链接时发生什么 |
|---|---|---|
静态库 | 多个 | 把需要的机器码/数据复制进 exe/dll |
导入库 | DLL 的“符号名片” | 不复制实现,只让链接器生成导入表/IAT |
动态库项目编译出来的那个.lib,是第二种。
二、导入库里“有”什么
以 MSVC 为例,Math.dll配套生成Math.lib,里面大致包含:
1. 导出符号清单
C 函数名:
AddC++ 修饰名:
?Add@@YANNN@Z变量符号:
?g_counter@@3HA可选 ordinal(导出序号)
作用:告诉链接器“这些名字合法,它们住在某个 DLL 里”。
2. 目标 DLL 名
每个导入项都绑定到具体 DLL:
Math.dll链接器据此在 exe 的导入表里写:
需要加载 Math.dll 需要解析符号 Add3. 短导入描述符(short import record)
MSVC 的导入库本质上是COFF/“short import” 记录的归档。
一条记录里会有:
machine(x86 / x64 / ARM64)
symbol name
DLL name
ordinal / hint
import-by-name 还是 import-by-ordinal
它不是普通.obj,但是能被link.exe读懂。
4. 桩 / 间接符号(__imp_机制)
对函数,导入库会提供导入符号:
Add:可能对应一个跳板/引用__imp_Add:指向 IAT 槽位(函数指针地址)
源码里写:
__declspec(dllimport) int Add(int, int);编译器会优先用__imp_Add,生成:
call qword ptr [__imp_Add]运行时 Windows 加载器把Math.dll里Add的真实地址填进这个槽。
5. 给链接器用的“重定位意图”
导入库不包含最终地址,但包含:
这是个外部符号
要走 IAT
加载时由系统回填
所以 exe 里看到的是:
Import Table
Import Address Table
对
__imp_xxx的引用
而不是Add的函数体。
三、导入库里“没有”什么
导入库不包含:
DLL 里函数的机器码
全局变量的实际内存
C++ 类成员函数的实现
运行时地址
完整 PDB / 源码 / 调试信息(PDB 是独立文件)
资源(对话框/图标/字符串表通常在 DLL 里)
所以:
删掉 DLL,只留
.lib:能编过链接,运行必炸。删掉
.lib,留 DLL:MSVC 链接器找不到符号,链接失败(除非手写LoadLibrary/GetProcAddress)。
四、为什么 Windows 需要导入库,Linux 不需要
Windows / PE
link.exe不能直接解析.dll来解析未定义符号需要
.lib做“编译期符号目录”运行时再由 NT 加载器填 IAT
Linux / ELF
.so自身带符号表ld可以直接读.so解析符号链接结果里写
DT_NEEDED: libfoo.so.1所以没有“导入库”这个概念,
.a只是静态库
一句话:
导入库是 PE 格式的历史/设计产物,不是 C++ 语言本身的要求。
五、用dumpbin看导入库在骗你之前先信它
dumpbin /exports Math.dll dumpbin /symbols Math.lib dumpbin /directives Math.lib你会看到:
.dll:真正有RVA、Export Table、代码段.lib:一堆UNDEF外部符号 +Math.dll引用 +__imp_*符号
六、C++ 项目里最容易混的三件事
1. 头文件
提供声明,不产生符号解析能力。
2. 导入库.lib
让链接器相信“符号存在,运行时再补地址”。
3. DLL.dll
运行时提供代码和数据。
缺头文件:编译报错
缺.lib:链接报错unresolved external
缺.dll:启动/运行时报错找不到模块或找不到入口点
七、一句话总结
动态库
.lib=导入库导入库 =符号名 + DLL 名 + ordinal/hint + IAT 桩
没有实现代码
没有运行时地址
只在链接期告诉
link.exe:“别慌,这函数开机后系统帮你填”Linux 的
.so把“导入库 + 部分链接信息”合一了,所以你看不到.lib