前言
C++20提供了模块功能,将其编译为动态链接库(或静态库)的方式与传统的头文件方式略所不同,本文将带你简要了解。静态库的编译方式与此类似,将在最后提及。
示例代码:点击下载
库项目配置
首先下载实例代码,可以看到一个 lib
文件,里面就是动态库的实例代码。
lib/
├── CMakeLists.txt
├── modules/Hello.cppm # 模块接口文件
└── src/Hello.cpp # 模块实现文件
你可以自行查看源码,已经给出了充分的注释。这里给出几个重点说明:
- 标准库模块可以不启用,自行删除相关代码。
- 模块接口文件需要
public
暴露给下层,模块实现文件可以private
隐藏。 - 可以仅接口文件(将函数实现也写在接口),但编译速度较慢。
- 模块接口文件必须使用
target_sources
命令配合CXX_MODULES
类型加入目标。 - 作为动态链接库,模块接口文件也需要编译器特定的导出符号(不是C++标准的export),但模块实现文件不需要。
- Clang/GNU下默认全导出,导出符号可以省略。MSVC的导出符号
__declspec(dllexport)
是必须的,但无需导入符号。
因此可以得到这么一份 CMakeLists.txt,与上面提供的示例代码相同:
cmake_minimum_required(VERSION 4.0.3)
# 标准库模块实验性 UUID ,请自行设置
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
# 添加动态库
set(lib_name "HelloLib")
add_library(${lib_name} SHARED)
# 获取源文件列表
file(GLOB_RECURSE CXX_MODULE_FILES "modules/*.cppm" "modules/*.ixx")
file(GLOB_RECURSE CXX_CPP_FILES "src/*.cpp")
# 添加源文件与模块接口文件
target_sources(${lib_name} PRIVATE ${CXX_CPP_FILES})
target_sources(${lib_name} PUBLIC
FILE_SET public_modules # FILE_SET任意取名
TYPE CXX_MODULES # 模块接口文件 需要使用 CXX_MODULES 类型
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/modules
FILES ${CXX_MODULE_FILES}
)
# 设置 C++ 标准和标准库模块支持
set_target_properties(${lib_name} PROPERTIES
CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON
CXX_MODULE_STD 1
)
# 设置动态链接库的导出符号,MSVC与Clang/GNU的处理方式不同。
if(MSVC)
target_compile_definitions(${lib_name} PRIVATE "LIB_EXPORT=__declspec(dllexport)")
else()
target_compile_options(${lib_name} PRIVATE -fvisibility=hidden)
target_compile_definitions(${lib_name} PRIVATE "LIB_EXPORT=__attribute__((visibility(\"default\")))")
endif()
使用模块的动态库
模块接口文件构成的动态库,使用起来和普通的动态库并无区别,依然使用 target_link_libraries
命令直接链接库即可,这边直接给出 CMakeLists.txt 代码,与上面的示例文件相同:
cmake_minimum_required(VERSION 4.0.3)
# 标准库模块实验性 UUID ,请自行设置
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
# 保证动态库文件和可执行文件输出到同目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<CONFIG>)
project(module_lib LANGUAGES CXX)
# 导入子目录
add_subdirectory(lib)
# 添加可执行文件目标
add_executable(main src/main.cpp)
# 链接动态库
target_link_libraries(main PRIVATE HelloLib)
# 设置 C++ 标准和标准库模块支持
set_target_properties(main PROPERTIES
CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON
CXX_MODULE_STD 1
)
然后在主项目的 main.cpp
文件中,可以直接使用 import
命令导入动态库中的模块:
import Hello;
int main() {
Hello::say();
}
静态库编译配置
静态库编译与动态库编译基本类似,只是无需设置编译器特定的导出符号。
参考示例代码,只需修改 CMakeLists.txt ,将库类型设为 STATIC
,并删除最下方的导出符号宏定义即可。
模块接口文件开头的宏配置
#ifndef LIB_EXPORT
#define LIB_EXPORT
#endif
可以保证 CMake 没有设置此宏时,将此宏定义为空,不影响模块接口文件内容。