Windows链接
本文目录 21 个章节
Windows链接
# Windows链接
- 静态链接(static)
.lib(静态库) → 代码被拷进最终产物- 不产生对该库 DLL 的运行时依赖
- 动态链接(dynamic)分两种
- 隐式动态链接:
.lib(导入库) +.dll(运行时) → 产物导入表依赖 DLL - 显式动态链接:运行时
LoadLibrary/GetProcAddress→ 不在导入表里硬依赖
1)从“编译到运行”的链路全貌
A. 编译(Compile)
输入:
.c/.cpp输出:
.obj(目标文件)里面有什么:
- 机器码(函数实现)
- 符号表(这个 obj 里定义了哪些函数/变量、引用了哪些外部符号)
此时 还没有把所有外部函数地址确定,比如你调用
GetProcessMemoryInfo,编译器只会记一个“外部符号引用”。
B. 链接(Link)
输入:很多
.obj+ 若干.lib(库)+ 以及一些默认系统库输出:
.exe或.dll链接器做两件大事:
- 符号解析:把“外部符号引用”匹配到“某个地方的定义”
- 地址重定位:把最终要调用/访问的地址关系安排好
链接有两条主要路径:
1)静态链接(Static link)
你链接到的是 静态库 .lib(里面真的包含实现代码)
链接器会把需要的
.obj代码复制到最终 exe/dll 里结果:
- 最终产物 不需要那个库的 DLL(因为代码已经被带进来)
- 文件可能更大
- 每个使用方都有一份拷贝(除非 LTO/COMDAT 合并等优化)
2)动态链接(Dynamic link,基于 DLL)
动态链接又分两种“建立依赖”的方式:
隐式动态链接(Implicit dynamic linking)
- 你在链接时使用一个 导入库 import lib(也是 .lib)
- 链接器不会把实现代码拷进来,而是生成 Import Table(导入表)
- 程序启动/模块加载时,Windows Loader 会加载对应
.dll并把函数地址填入 IAT - 如果 DLL 缺失,可能导致加载失败/进程启动失败(取决于该模块什么时候被加载)
显式动态链接(Explicit dynamic linking)
运行时你自己调用:
LoadLibrary("xxx.dll")GetProcAddress("Func")
这时链接器不需要 import lib(或只需要 kernel32 的这些 API)
如果 DLL 缺失,你可以选择优雅降级,不会因为导入表缺失而加载失败
2)“库文件”到底分哪些?(Windows 视角)
1)DLL:动态链接库(运行时实体)
.dll是真正的动态库文件,装着实现代码- 运行时会被加载到进程地址空间里
2)LIB:.lib 不是一种“库类型”,而是两种不同东西的容器名(关键)
在 MSVC/Windows 生态里,.lib 可能是:
A) 静态库(static library)
- 里面包含真实的
.obj代码(实现) - 用于静态链接:代码会被拷进最终 exe/dll
B) 导入库(import library)
- 里面通常不包含实现代码(或只包含很小的跳板/描述信息)
- 它告诉链接器:某些符号来自某个 DLL
- 用于隐式动态链接:最终 exe/dll 里会生成导入表,运行时需要对应 DLL
所以:“.lib 既可能是静态库,也可能是导入库” 光看扩展名看不出来,必须结合来源/用途/工具检查(dumpbin 等)。
Android与Windows的区别
是的,这里确实体现了 Windows vs Android 在“动态链接”工程化形态上的差异。但本质机制并不矛盾:两边都可以“链接到动态库”,只是Windows 把“链接到 DLL”通常分成了两件文件(.lib + .dll),而 Android/Linux 通常直接用 .so 及其符号/SONAME 体系,不需要“import lib”这种额外壳。
下面按你关心的点说明:import lib 是什么、为什么 Android 没这个概念、两边动态链接流程差异、CMakeLists 写法差异。
1)为什么 Android 平台很少听说 “import lib”?
Android/Linux 的常规做法
动态库是
.so,里面既有实现代码,也包含动态符号表(可供其他模块在链接/运行时解析)。链接一个共享库时,链接器用的是:
libxxx.so(或libxxx.so的 linker stub)- 生成可执行文件/共享库里的 DT_NEEDED 依赖条目
运行时由 dynamic loader(
linker/ld-android.so)加载.so,按 ELF 机制做重定位与符号解析。
Windows 的常规做法
动态库是
.dll,但 MSVC 的链接器通常不直接用.dll做链接输入。MSVC 使用一个配套的 导入库(import library)
xxx.lib:- 它像“链接期说明书”,告诉链接器:这些符号来自哪个 DLL、如何生成导入表(IAT)。
运行时由 Windows Loader 根据导入表加载
.dll并填充 IAT。
2)两边动态链接的“工程形态”差异总结
Windows(PE/COFF)
- 链接期:通常用
xxx.lib(import lib)来链接 - 运行期:加载
xxx.dll - 显式动态加载:
LoadLibrary+GetProcAddress
Android/Linux(ELF)
- 链接期:用
libxxx.so(或开发包提供的 stub so) - 运行期:加载
libxxx.so - 显式动态加载:
dlopen+dlsym
3)CMakeLists 写法有什么差异?
A. 链接一个“系统库/平台库”的差异
Windows:
target_link_libraries(my_target PRIVATE psapi)
- 这会让链接器去找
psapi.lib(import lib),最终运行时依赖psapi.dll。 - 如果你写了
#pragma comment(lib,"psapi.lib"),那是 MSVC 私有的做法;CMake 更推荐用target_link_libraries。
Android:
target_link_libraries(my_target PRIVATE log android)
- Android NDK 里
log/android等是系统 so(实际是liblog.so/libandroid.so)。 - 不存在“额外 import lib”的概念。
B. 链接一个第三方动态库的差异(你自己带的库)
Android:
你通常直接链接 .so:
add_library(foo SHARED IMPORTED)
set_target_properties(foo PROPERTIES
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libfoo.so"
)
target_link_libraries(my_target PRIVATE foo)
Windows:
第三方库通常给你一套:foo.dll + foo.lib
- 你链接的是
.lib,运行时需要.dll:
add_library(foo SHARED IMPORTED)
set_target_properties(foo PROPERTIES
IMPORTED_IMPLIB "${CMAKE_SOURCE_DIR}/libs/foo.lib" # 链接期用
IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/bin/foo.dll" # 运行时用(可选写)
)
target_link_libraries(my_target PRIVATE foo)
关键差别:Windows 下 CMake 要区分
IMPORTED_IMPLIB(导入库)和IMPORTED_LOCATION(DLL 本体)。Android 下通常只要IMPORTED_LOCATION指向.so。
C. “我不想硬依赖某个动态库”的写法差异(SDK 很常用)
Windows 显式加载:
不在 CMake 链接
psapi(也不#pragma comment(lib))运行时:
LoadLibraryW(L"psapi.dll")GetProcAddress(...)
好处:缺失时可降级,不影响模块加载/进程启动。
Android 显式加载:
不在 CMake 链接
libxxx.so运行时:
dlopen("libxxx.so", RTLD_NOW)dlsym(...)
同样是可降级策略。