使用 CMake 创建并导出动态库

使用 CMake 创建并导出动态库
使用 CMake 创建并导出动态库本文说明cmake-lib如何生成、安装并导出动态库使其他 CMake项目能够通过find_package()使用它们。配套的消费端说明见在 CMake 项目中使用动态库。1. 项目概览cmake-lib使用 C17生成两个彼此独立的动态库CMake 目标动态库职责公开接口message_utils构造示例提示信息std::string build_message()sort_utils返回排序后的整数副本bubble_sort、selection_sort、insertion_sort、merge_sort、quick_sort排序接口位于sort_algorithms命名空间中参数和返回值均为std::vectorint。项目的主要结构如下cmake-lib/ ├── CMakeLists.txt ├── CMakePresets.json ├── cmake-lib-config.cmake.in ├── .vscode/ │ └── tasks.json └── src/ ├── CMakeLists.txt └── utils/ ├── CMakeLists.txt ├── message/ │ ├── CMakeLists.txt │ ├── message.h │ └── message.cpp └── sort/ ├── CMakeLists.txt ├── sort_algorithms.h └── sort_algorithms.cpp2. 本文 MinGW 演示环境Windows 与 PowerShell 7pwshCMake 3.25 或更高版本MinGW 的gcc、g和mingw32-make已加入PATH这些是本文端到端示例的环境要求不是 CMake 工程本身只能运行在 Windows上的限制。工程还提供了 Ninja/Clang preset在其他平台上SHARED目标会生成该平台对应的动态库格式。根CMakeLists.txt声明的最低版本是 CMake 3.16但CMakePresets.json使用 schema version 6。通过本文的 preset 命令构建时实际需要 CMake 3.25 或更高版本。mingwpreset 的显示名称中包含旧的 GCC 版本号这只是界面标签不是编译器版本限制。3. CMake 如何生成动态库3.1 根目录定义工程并进入源码目录根CMakeLists.txt定义项目版本、C17 标准并通过add_subdirectory(src)进入源码目录cmake_minimum_required(VERSION 3.16) project(cmake-lib VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(src)3.2 中间目录自动加入功能子目录src/CMakeLists.txt和src/utils/CMakeLists.txt扫描直接子目录并对每个目录调用add_subdirectory()。因此真正定义目标的是最末级的message/CMakeLists.txt和sort/CMakeLists.txt。3.3 叶子目录显式创建SHARED目标两个目标都显式使用SHARED因此不依赖BUILD_SHARED_LIBSadd_library(message_utils SHARED ${MESSAGE_UTILS_SOURCES} ) add_library(sort_utils SHARED ${SORT_UTILS_SOURCES} )目标使用PUBLICinclude 目录target_include_directories(message_utils PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR} $INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR} )在当前源码树中构建时调用方可以从库的源码目录找到头文件。INSTALL_INTERFACE表达了安装后使用 include 目录的意图。sort_utils使用相同方式配置。当前根CMakeLists.txt在add_subdirectory(src)之后才加载GNUInstallDirs因此全新配置时叶子目录中的${CMAKE_INSTALL_INCLUDEDIR}可能尚未初始化。最终导出目标仍能获得prefix/include是因为根安装规则同时使用了INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}。如果以后调整 CMake代码宜把include(GNUInstallDirs)移到add_subdirectory(src)之前。4. 安装与 CMake 包导出根CMakeLists.txt使用GNUInstallDirs和CMakePackageConfigHelpers完成四类安装内容。4.1 安装头文件公开头文件被安装到prefix/include/cmake-lib/message.h prefix/include/cmake-lib/sort_algorithms.h因此消费端使用#includecmake-lib/message.h#includecmake-lib/sort_algorithms.h4.2 安装动态库和导入库两个目标被加入cmake-lib-targets导出集install( TARGETS message_utils sort_utils EXPORT cmake-lib-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} )在 MinGW/Windows 下RUNTIME是运行时加载的.dll安装到prefix/bin。ARCHIVE是链接阶段使用的.dll.a导入库安装到prefix/lib。4.3 导出带命名空间的目标导出集安装到prefix/lib/cmake/cmake-lib并添加cmake-lib::命名空间install( EXPORT cmake-lib-targets FILE cmake-lib-targets.cmake NAMESPACE cmake-lib:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cmake-lib )消费端最终链接的不是文件路径而是两个导入目标cmake-lib::message_utils cmake-lib::sort_utils导出文件记录了头文件目录、导入库位置和 DLL 位置。它不会自动复制 DLL也不会修改 Windows 的运行时搜索路径运行阶段仍需按第 7 节配置。4.4 生成包配置和版本文件cmake-lib-config.cmake.in会加载导出的 targets 文件PACKAGE_INIT include(${CMAKE_CURRENT_LIST_DIR}/cmake-lib-targets.cmake) check_required_components(cmake-lib)项目还会生成cmake-lib-config-version.cmake版本兼容规则为SameMajorVersion。这些文件让消费端可以使用find_package(cmake-lib REQUIRED)project(... VERSION 1.0.0)当前只用于生成包版本文件。两个动态库目标没有设置VERSION或SOVERSION因此项目版本不会自动转换成动态库文件名或soname 的二进制版本。5. 使用 MinGW 构建和安装在cmake-lib目录中运行cmake--preset mingw cmake--build--preset mingwbuild cmake--install build/mingw--prefix C:/install/cmake-lib三条命令分别完成使用MinGW Makefiles配置 Debug 构建并生成build/mingw/compile_commands.json。编译message_utils和sort_utils。把库、头文件和 CMake 包配置安装到C:/install/cmake-lib。未安装前MinGW 构建产物位于各目标自己的构建子目录例如build/mingw/src/utils/message/ ├── libmessage_utils.dll └── libmessage_utils.dll.a build/mingw/src/utils/sort/ ├── libsort_utils.dll └── libsort_utils.dll.a安装后的典型结构如下C:/install/cmake-lib/ ├── bin/ │ ├── libmessage_utils.dll │ └── libsort_utils.dll ├── include/ │ └── cmake-lib/ │ ├── message.h │ └── sort_algorithms.h └── lib/ ├── libmessage_utils.dll.a ├── libsort_utils.dll.a └── cmake/ └── cmake-lib/ ├── cmake-lib-config.cmake ├── cmake-lib-config-version.cmake ├── cmake-lib-targets.cmake └── cmake-lib-targets-debug.cmake6. 使用 Ninja/Clang 构建库库项目还提供了ninja-clang和clangbuildcmake--preset ninja-clang cmake--build--preset clangbuild cmake--install build/ninja-clang--prefix C:/install/cmake-lib-clang该流程要求clang、clang和ninja已加入PATH。当前cmake-use-lib只提供 MinGW preset因此本文不把 Clang 产物描述为已验证的MinGW 消费流程。该 preset 同样生成 Debug 配置示例使用独立安装前缀避免覆盖 MinGW 产物。安装和消费时应让生产者与消费者使用 ABI 兼容的编译器、标准库、目标架构和构建配置。7. Windows 和 ABI 注意事项7.1 链接成功不代表运行时能找到 DLL.dll.a只用于链接。程序启动时Windows 仍必须能找到真正的 DLL。推荐在当前 pwsh 会话中把安装目录的bin加入PATH$env:PATH C:\install\cmake-lib\bin;$env:PATH也可以把 DLL 复制到可执行文件目录但复制的 DLL 容易与新构建版本不一致。当前安装规则只安装本项目的两个 DLL不会自动部署libstdc、libgcc或线程库等 MinGW 运行时依赖。将程序复制到未安装 MinGW 的机器时还需一并处理这些运行时依赖。7.2 保持工具链兼容公开接口跨 DLL 边界传递std::string和std::vectorint。库与消费程序必须使用兼容的编译器、C 标准库、位数和构建配置尤其不要混用 MinGW 与MSVC 产物。7.3 当前导出方式的范围公开头文件目前没有__declspec(dllexport/dllimport)导出宏项目也没有启用WINDOWS_EXPORT_ALL_SYMBOLS。当前 MinGW 流程依赖链接器自动导出符号不能据此保证 MSVC 或其他 Windows ABI 组合可直接使用。8. 常见问题现象检查方法找不到gcc、g或mingw32-make确认 MinGW 工具目录已加入PATHpreset 无法读取使用 CMake 3.25 或更高版本安装目录没有 DLL先成功执行构建再执行cmake --install消费端找不到包检查prefix/lib/cmake/cmake-lib/cmake-lib-config.cmake是否存在链接时报未定义符号检查工具链、架构和配置是否一致并检查 DLL 导出宏或自动导出设置程序启动时提示缺少 DLL把prefix/bin加入当前进程的PATH

最新新闻

日新闻

周新闻

月新闻