强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

CMake 从入门到精通:完整教程 / 第 6 章:库的构建与使用

第 6 章:库的构建与使用

6.1 库的类型总览

类型CMake 关键字文件扩展名链接方式使用场景
静态库STATIC.a / .lib编译时嵌入内部工具库、单体部署
动态库SHARED.so / .dll / .dylib运行时加载插件系统、减少体积
对象库OBJECT.o 文件集合编译时嵌入避免重复编译
接口库INTERFACE仅传递属性纯头文件库
模块库MODULE.so / .dll运行时 dlopen插件

6.2 静态库

6.2.1 创建静态库

add_library(mylib STATIC
    src/parser.cpp
    src/lexer.cpp
    src/codegen.cpp
)

target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

target_compile_features(mylib PUBLIC cxx_std_17)

6.2.2 设置静态库属性

add_library(mylib STATIC src/mylib.cpp)

# 输出名称
set_target_properties(mylib PROPERTIES
    OUTPUT_NAME "mylib"           # libmylib.a
    PREFIX "lib"                  # 前缀
    ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)

# PIC(位置无关代码)— 通常静态库不需要
# 但如果静态库要链接到动态库中,则需要
set_target_properties(mylib PROPERTIES POSITION_INDEPENDENT_CODE ON)

6.2.3 控制库的构建

# BUILD_SHARED_LIBS 控制默认库类型
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

# 这样 add_library 默认创建静态库
add_library(mylib src/mylib.cpp)
# 等价于 add_library(mylib STATIC src/mylib.cpp)

# 用户可以通过 -DBUILD_SHARED_LIBS=ON 来切换为动态库

6.3 动态库(共享库)

6.3.1 创建动态库

add_library(mylib SHARED
    src/parser.cpp
    src/lexer.cpp
)

# 设置版本号
set_target_properties(mylib PROPERTIES
    VERSION "2.1.0"       # 完整版本号 → libmylib.so.2.1.0
    SOVERSION "2"         # SO 版本   → libmylib.so.2
)

# 生成符号可见性头文件
generate_export_header(mylib
    BASE_NAME MYLIB
    EXPORT_MACRO_NAME MYLIB_EXPORT
    EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/include/mylib/export.h
    STATIC_DEFINE MYLIB_BUILT_AS_STATIC
)

6.3.2 符号导出(Windows 必需)

# CMake 自动生成的 export 头文件
generate_export_header(mylib)

生成的 mylib_export.h

#ifndef MYLIB_EXPORT_H
#define MYLIB_EXPORT_H

#ifdef MYLIB_STATIC_DEFINE
#  define MYLIB_EXPORT
#  define MYLIB_NO_EXPORT
#else
#  ifndef MYLIB_EXPORT
#    ifdef mylib_EXPORTS
       /* 正在构建共享库 */
#      define MYLIB_EXPORT __declspec(dllexport)
#    else
       /* 正在使用共享库 */
#      define MYLIB_EXPORT __declspec(dllimport)
#    endif
#  endif
#endif

#endif

在源码中使用:

// parser.h
#include "mylib/export.h"

class MYLIB_EXPORT Parser {
public:
    void parse(const std::string& input);
};

// 可选:隐藏某些函数
class Parser {
public:
    MYLIB_EXPORT void publicMethod();
    void privateMethod();  // 不导出
};

6.3.3 动态库版本管理

Linux 上的 SO 版本机制:
libmylib.so → libmylib.so.2 → libmylib.so.2.1.0

libmylib.so.2.1.0   ← 实际文件
libmylib.so.2       ← SONAME(运行时链接器使用)
libmylib.so         ← 开发链接使用(符号链接)
set_target_properties(mylib PROPERTIES
    VERSION ${PROJECT_VERSION}          # 2.1.0
    SOVERSION ${PROJECT_VERSION_MAJOR}  # 2
)

6.3.4 Windows DLL 特殊处理

# Windows 上动态库需要导入库(.lib)
# CMake 自动处理,但需要注意路径
set_target_properties(mylib PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin   # .dll
    ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib   # .lib(导入库)
)

6.4 对象库

6.4.1 基本用法

# 对象库不产生 .a 或 .so 文件
add_library(mylib_objects OBJECT
    src/a.cpp
    src/b.cpp
    src/c.cpp
)

# 设置编译属性(这些属性可以传递给链接者)
target_include_directories(mylib_objects PUBLIC include/)
target_compile_definitions(mylib_objects PUBLIC HAS_FEATURE_X)
target_compile_features(mylib_objects PUBLIC cxx_std_17)

# 使用对象库创建静态库
add_library(mylib_static STATIC $<TARGET_OBJECTS:mylib_objects>)

# 使用对象库创建动态库
add_library(mylib_shared SHARED $<TARGET_OBJECTS:mylib_objects>)

6.4.2 直接链接对象库(CMake 3.12+)

add_library(mylib_objects OBJECT src/a.cpp src/b.cpp)
target_include_directories(mylib_objects PUBLIC include)

# 直接链接,无需 $<TARGET_OBJECTS:>
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib_objects)

6.4.3 对象库的传播特性

add_library(mylib_objects OBJECT src/mylib.cpp)
target_include_directories(mylib_objects PUBLIC include)
target_compile_definitions(mylib_objects PRIVATE INTERNAL_DEBUG)

# 链接时,PUBLIC 属性会传播
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib_objects)
# app 会获得 include 目录
# app 不会获得 INTERNAL_DEBUG 定义(PRIVATE)

6.5 find_package

6.5.1 基本用法

# 查找 OpenSSL
find_package(OpenSSL REQUIRED)

# 使用找到的库
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)

6.5.2 Config 模式 vs Module 模式

# find_package 有两种查找模式:
# 1. Config 模式:查找 <PackageName>Config.cmake 或 <lower>-config.cmake
# 2. Module 模式:查找 Find<PackageName>.cmake

# 指定 Config 模式
find_package(OpenSSL CONFIG REQUIRED)

# 指定 Module 模式
find_package(OpenSSL MODULE REQUIRED)

# 两种模式都尝试(默认行为)
find_package(OpenSSL REQUIRED)

6.5.3 查找路径

# 命令行指定
# cmake -S . -B build -DCMAKE_PREFIX_PATH="/opt/deps;/usr/local"

# CMakeLists.txt 中设置
list(APPEND CMAKE_PREFIX_PATH "/opt/deps")

# 专用于某个包
# cmake -S . -B build -DOPENSSL_ROOT_DIR=/opt/openssl

6.5.4 版本约束

# 要求最低版本
find_package(Boost 1.70 REQUIRED)

# 精确版本
find_package(Boost 1.70.0 EXACT REQUIRED)

# 版本范围(CMake 3.19+)
find_package(Boost 1.70...1.85 REQUIRED)

6.5.5 组件

# 查找特定组件
find_package(Boost REQUIRED COMPONENTS filesystem system thread)

# 使用组件
target_link_libraries(myapp PRIVATE
    Boost::filesystem
    Boost::system
    Boost::thread
)

# 可选组件
find_package(Qt6 QUIET COMPONENTS Widgets Network)
if(Qt6_FOUND)
    target_link_libraries(myapp PRIVATE Qt6::Widgets Qt6::Network)
endif()

6.5.6 处理查找结果

find_package(OpenSSL)

if(OpenSSL_FOUND)
    message("OpenSSL 版本: ${OPENSSL_VERSION}")
    message("OpenSSL 包含目录: ${OPENSSL_INCLUDE_DIR}")
    target_link_libraries(myapp PRIVATE OpenSSL::SSL)
else()
    message(WARNING "OpenSSL 未找到,禁用 SSL 功能")
    target_compile_definitions(myapp PRIVATE DISABLE_SSL)
endif()

6.5.7 QUIET 和 REQUIRED

# REQUIRED:找不到则报错
find_package(OpenSSL REQUIRED)

# QUIET:不输出信息
find_package(OpenSSL QUIET)

# 两者结合
find_package(OpenSSL QUIET REQUIRED)

# 先尝试查找,找不到用替代方案
find_package(OpenSSL QUIET)
if(NOT OpenSSL_FOUND)
    message(STATUS "使用内置 OpenSSL 实现")
    add_subdirectory(third_party/boringssl)
endif()

6.6 pkg-config

6.6.1 使用 pkg-config 查找库

# 使用 CMake 的 pkg-config 模块
find_package(PkgConfig REQUIRED)

# 查找单个包
pkg_check_modules(OPENSSL REQUIRED openssl)

# 使用结果
target_include_directories(myapp PRIVATE ${OPENSSL_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${OPENSSL_LIBRARIES})
target_compile_options(myapp PRIVATE ${OPENSSL_CFLAGS_OTHER})

# 使用 IMPORTED 目标(推荐)
pkg_check_modules(OPENSSL REQUIRED IMPORTED_TARGET openssl)
target_link_libraries(myapp PRIVATE PkgConfig::OPENSSL)

6.6.2 多个包

pkg_check_modules(
    MY_DEPS REQUIRED
    IMPORTED_TARGET
    gtk+-3.0
    glib-2.0
    pango
)

target_link_libraries(myapp PRIVATE PkgConfig::MY_DEPS)

6.6.3 静态链接

# 使用静态链接
pkg_check_modules(LIBSSH2 REQUIRED STATIC libssh2)

6.6.4 pkg_check_modules 变量

变量说明
<PREFIX>_FOUND是否找到
<PREFIX>_VERSION版本号
<PREFIX>_INCLUDE_DIRS包含目录
<PREFIX>_LIBRARIES库文件
<PREFIX>_LINK_LIBRARIES链接库(完整路径)
<PREFIX>_CFLAGS编译标志
<PREFIX>_CFLAGS_OTHER其他编译标志

6.7 find_library

# 查找单个库文件
find_library(MATH_LIBRARY m)
if(MATH_LIBRARY)
    target_link_libraries(myapp PRIVATE ${MATH_LIBRARY})
endif()

# 指定搜索路径
find_library(SSL_LIBRARY
    NAMES ssl libssl
    PATHS /opt/openssl/lib
    NO_DEFAULT_PATH
)

# 查找头文件
find_path(SSL_INCLUDE_DIR
    NAMES openssl/ssl.h
    PATHS /opt/openssl/include
)

6.8 库的链接策略

6.8.1 链接顺序

# 链接顺序很重要(静态库)
# 依赖者放在前面,被依赖者放在后面
target_link_libraries(app PRIVATE mylib utils base pthread)
# 顺序:app → mylib → utils → base → pthread

6.8.2 链接器标志

# 传递链接器选项
target_link_options(mylib PRIVATE
    -Wl,--no-undefined    # 检查未定义符号(Linux)
    -Wl,-z,defs           # 类似效果
)

# 设置链接依赖
set_target_properties(mylib PROPERTIES
    LINK_DEPENDS_NO_SHARED ON  # 不因为共享库变化而重新链接
)

6.8.3 循环依赖

# 静态库之间循环依赖时,需要特殊处理

# 方式一:使用链接组
target_link_libraries(app PRIVATE
    -Wl,--start-group
    libA
    libB
    -Wl,--end-group
)

# 方式二:合并为一个库
target_link_libraries(libA PUBLIC libB)
target_link_libraries(libB PUBLIC libA)

# 方式三:使用对象库(推荐)
add_library(libA OBJECT src/a.cpp)
add_library(libB OBJECT src/b.cpp)
target_link_libraries(libA PUBLIC libB)
target_link_libraries(libB PUBLIC libA)

6.9 业务场景

场景:构建一个可选依赖的库

option(USE_OPENSSL "使用 OpenSSL 支持" ON)
option(USE_ZLIB "使用 zlib 压缩支持" ON)

add_library(mylib src/core.cpp)

if(USE_OPENSSL)
    find_package(OpenSSL REQUIRED)
    target_link_libraries(mylib PUBLIC OpenSSL::SSL)
    target_compile_definitions(mylib PUBLIC HAS_OPENSSL)
endif()

if(USE_ZLIB)
    find_package(ZLIB REQUIRED)
    target_link_libraries(mylib PRIVATE ZLIB::ZLIB)
    target_compile_definitions(mylib PRIVATE HAS_ZLIB)
endif()

6.10 注意事项

问题说明
静态库不链接其他库静态库本身不进行链接,链接在最终可执行文件时进行
Windows DLL 符号导出Windows 上必须显式导出符号
Linux SO 版本设置 SOVERSION 管理 ABI 兼容性
find_package 顺序设置 CMAKE_PREFIX_PATH 或具体包的 *_ROOT_DIR
pkg-config 可能不可用Windows 上较少使用 pkg-config

6.11 扩展阅读


上一章:第 5 章 — 目标与属性 | 下一章:第 7 章 — 查找模块详解 →