第 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 章 — 查找模块详解 →