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)

就这么简单。但这里有个关键点:PRIVATEPUBLICINTERFACE 这三个关键字,很多人搞不清楚。我刚开始也踩过坑。

7.4 PUBLIC / PRIVATE / INTERFACE:到底啥区别?

这三个关键字,决定了依赖关系的传递方式。我画个图帮你理解:

依赖传递规则:PUBLIC / PRIVATE / INTERFACE 模块 A 模块 B 模块 C PUBLIC PRIVATE 传递规则说明: • PUBLIC:A 链接 B,且 B 的依赖对 A 可见(传递) • PRIVATE:A 链接 B,但 B 的依赖对 A 不可见(不传递) • INTERFACE:A 不链接 B,但 B 的依赖对 A 可见(仅传递接口) 举个例子:B 用 PRIVATE 链接 C,那么 A 看不到 C 的任何头文件或库。

说白了:

  • PRIVATE:我自己的依赖,我自己用,不传给下游。比如我的库内部用了 zlib,但外部不需要知道。
  • PUBLIC:我的依赖,下游也需要。比如我的库暴露了 Eigen 类型,那下游必须也链接 Eigen。
  • INTERFACE:我自己不链接,但下游必须链接。常见于纯头文件库或接口库。
核心原则: 能用 PRIVATE 就别用 PUBLIC。过度传递依赖,会让你的构建图变得一团乱麻。我曾经接手过一个项目,PUBLIC 满天飞,改一个库的依赖,整个项目重新编译半小时。后来我花了一周重构,全部改成 PRIVATE,编译时间直接砍半。

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。

注意: 动态库在 Windows 上需要导出符号。你需要在头文件里加 __declspec(dllexport)__declspec(dllimport)。这个细节我当年折腾了一整天,后来用 CMake 的 GenerateExportHeader 模块才搞定。

7.6 避坑指南:我踩过的几个坑

  • 链接顺序问题:静态库的链接顺序很重要。如果 A 依赖 B,那 A 必须出现在 B 前面。我曾经在 Linux 上遇到链接错误,调了半天顺序才解决。后来我改用 target_link_librariesINTERFACE 机制,才彻底摆脱这个问题。
  • 循环依赖:两个库互相引用,CMake 会报错。解决办法是拆库,或者用 INTERFACE 库做中间层。
  • 头文件路径:别忘了用 target_include_directories 指定头文件路径。我见过有人把路径写死在编译命令里,结果换台机器就编译不过。

7.7 小结

库的创建与链接,是 CMake 的基石。你掌握了 add_librarytarget_link_libraries 和 PUBLIC/PRIVATE/INTERFACE 这三个关键字,就能搭建出清晰、可维护的构建系统。记住:依赖传递要克制,能用 PRIVATE 就别用 PUBLIC。这是我从无数次重构中总结出来的教训。

好,今天就聊到这儿。下次咱们聊聊如何用 CMake 管理第三方依赖——那又是一个新世界。

公众号:蓝海资料掘金营,微信 deep3321