7、CMake库的创建与链接:add_library、静态库与动态库、target_link_libraries、PUBLIC/PRIVATE/INTERFACE
好,咱们今天聊点实在的。库的创建与链接,说白了就是CMake里最核心的基建工作。你写了几百行代码,总不能全塞在一个文件里吧?得拆成模块,编译成库,再让其他目标去链接。这套流程,我几乎每天都在用。
7.1 用 add_library 创建你的第一个库
先看最基础的用法。你想把一堆源文件打包成一个库,用 add_library 就对了。
add_library(my_lib STATIC src/lib.cpp src/utils.cpp)
这里 STATIC 表示静态库。如果你想要动态库,换成 SHARED 就行:
add_library(my_lib SHARED src/lib.cpp src/utils.cpp)
还有一种叫 MODULE,用得少,我暂时不提它,免得你混淆。
network,源文件就放 network/ 下。这样找起来不费劲。
7.2 静态库 vs 动态库:怎么选?
这个问题,我当年也纠结过。简单说两句:
| 特性 | 静态库(.a / .lib) | 动态库(.so / .dll) |
|---|---|---|
| 链接方式 | 编译时直接嵌入可执行文件 | 运行时加载,可执行文件只存引用 |
| 文件大小 | 可执行文件变大 | 可执行文件小,但需要额外分发 .so |
| 更新维护 | 改库需要重新链接所有依赖 | 替换 .so 即可,不用重新编译主程序 |
| 适用场景 | 小工具、嵌入式、不想处理依赖 | 插件系统、大型项目、需要热更新 |
我在项目中遇到过一个问题:用静态库时,链接顺序错了,导致一堆未定义符号。后来我改用动态库,反而省心不少。不过动态库也有坑——版本兼容问题,这个后面再聊。
7.3 target_link_libraries:把库连起来
库建好了,怎么让可执行文件用上它?用 target_link_libraries。
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE my_lib)
就这么简单。但这里有个关键点:PRIVATE、PUBLIC、INTERFACE 这三个关键字,很多人搞不清楚。我刚开始也踩过坑。
7.4 PUBLIC / PRIVATE / INTERFACE:到底啥区别?
这三个关键字,决定了依赖关系的传递方式。我画个图帮你理解:
说白了:
- PRIVATE:我自己的依赖,我自己用,不传给下游。比如我的库内部用了
zlib,但外部不需要知道。 - PUBLIC:我的依赖,下游也需要。比如我的库暴露了
Eigen类型,那下游必须也链接 Eigen。 - INTERFACE:我自己不链接,但下游必须链接。常见于纯头文件库或接口库。
7.5 实战:一个完整的例子
咱们写个完整的小项目,把上面这些串起来。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject)
# 创建静态库
add_library(math_utils STATIC src/math.cpp)
target_include_directories(math_utils PUBLIC include)
# 创建动态库
add_library(io_utils SHARED src/io.cpp)
target_include_directories(io_utils PUBLIC include)
target_link_libraries(io_utils PRIVATE math_utils)
# 创建可执行文件
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE io_utils)
你看,io_utils 用 PRIVATE 链接 math_utils,所以 my_app 不需要知道 math_utils 的存在。但如果 io_utils 的头文件里引用了 math_utils 的类型,那就得改成 PUBLIC。
__declspec(dllexport) 和 __declspec(dllimport)。这个细节我当年折腾了一整天,后来用 CMake 的 GenerateExportHeader 模块才搞定。
7.6 避坑指南:我踩过的几个坑
- 链接顺序问题:静态库的链接顺序很重要。如果 A 依赖 B,那 A 必须出现在 B 前面。我曾经在 Linux 上遇到链接错误,调了半天顺序才解决。后来我改用
target_link_libraries的INTERFACE机制,才彻底摆脱这个问题。 - 循环依赖:两个库互相引用,CMake 会报错。解决办法是拆库,或者用
INTERFACE库做中间层。 - 头文件路径:别忘了用
target_include_directories指定头文件路径。我见过有人把路径写死在编译命令里,结果换台机器就编译不过。
7.7 小结
库的创建与链接,是 CMake 的基石。你掌握了 add_library、target_link_libraries 和 PUBLIC/PRIVATE/INTERFACE 这三个关键字,就能搭建出清晰、可维护的构建系统。记住:依赖传递要克制,能用 PRIVATE 就别用 PUBLIC。这是我从无数次重构中总结出来的教训。
好,今天就聊到这儿。下次咱们聊聊如何用 CMake 管理第三方依赖——那又是一个新世界。