如何在CMake中从PCH编译排除单个C++编译定义?
我正在开发一款跨平台C++产品,其中一个目标平台是Windows+MSVC,使用CMake作为构建系统。我为共享库lib设置了预编译头pch.h:
target_precompile_headers( lib PUBLIC pch.h )
之后添加了一个链接到lib的可执行文件exe,打算通过target_precompile_headers的REUSE_FROM选项复用已编译的预编译头:
target_precompile_headers( exe REUSE_FROM lib ) target_link_libraries( exe lib )
但此时VS编译时出现警告:
warning C4651: '/Dlib_EXPORTS' specified for precompiled header but not for current compile
我知道lib_EXPORTS是CMake构建共享库lib时自动添加的编译定义,用于导出库的符号。我希望这个定义仅作用于lib的.cpp文件,而不作用于pch.h的编译过程。
我不考虑以下两种方案:
- 直接抑制该警告;
- 创建单独目标专门编译预编译头,再供
lib和exe复用——这种方式需要额外维护目标,多库多可执行文件场景下工作量会增加。
请问有没有办法让CMake在编译pch.h时排除lib_EXPORTS定义?我已经尝试过set_source_files_properties(pch.h ...)但没有效果。
最小复现示例
CMakeLists.txt
cmake_minimum_required(VERSION 3.13) project(pch_extra_def) add_library(lib SHARED lib.cpp) target_precompile_headers( lib PUBLIC pch.h ) add_executable(exe exe.cpp) target_precompile_headers( exe REUSE_FROM lib ) target_link_libraries( exe lib ) set_target_properties( exe PROPERTIES COMPILE_WARNING_AS_ERROR ON )
pch.h
#include <iostream>
lib.h
#ifdef lib_EXPORTS #define lib_API __declspec(dllexport) #else #define lib_API __declspec(dllimport) #endif lib_API void f();
lib.cpp
#include "pch.h" #include "lib.h" void f() { std::cout<<"Hello, "; }
exe.cpp
#include "pch.h" #include "lib.h" int main() { f(); std::cout<<"World!"<<std::endl; return 0; }
可以通过调整编译定义的作用范围,让lib_EXPORTS仅应用于lib的源文件(而非目标全局),同时配合预编译头的编译逻辑控制,具体步骤如下:
步骤1:移除lib目标的全局lib_EXPORTS定义
CMake为共享库自动添加的lib_EXPORTS是目标级别的编译定义,会作用于该目标的所有编译单元(包括预编译头的编译)。我们需要先清除这个全局定义,再手动将其仅添加到lib的.cpp源文件上:
add_library(lib SHARED lib.cpp) # 移除CMake自动添加的全局lib_EXPORTS定义 target_compile_definitions(lib PRIVATE "$<$<NOT:$<BOOL:${MSVC}>>:lib_EXPORTS>") # 仅给lib.cpp添加lib_EXPORTS定义 set_source_files_properties(lib.cpp PROPERTIES COMPILE_DEFINITIONS lib_EXPORTS)
这里的条件表达式是为了兼容非MSVC平台——如果你的跨平台逻辑不需要额外处理,也可以直接用target_compile_definitions(lib PRIVATE "")清空全局的lib_EXPORTS。
步骤2:保留原预编译头与复用逻辑
由于lib_EXPORTS已经被限定在lib.cpp上,编译pch.h时就不会包含这个定义了,直接保留原有的预编译头和可执行文件复用配置即可:
target_precompile_headers( lib PUBLIC pch.h ) add_executable(exe exe.cpp) target_precompile_headers( exe REUSE_FROM lib ) target_link_libraries( exe lib ) set_target_properties( exe PROPERTIES COMPILE_WARNING_AS_ERROR ON )
原理说明
- 目标级别的编译定义会覆盖该目标下所有编译单元,包括预编译头的编译过程;而源文件级别的编译定义仅作用于指定的
.cpp文件。 - 预编译头的编译是独立的单元,不会继承单个源文件的编译定义,因此编译
pch.h时不会带有lib_EXPORTS,彻底规避MSVC的C4651警告,同时不影响lib的符号导出逻辑。
完整验证代码
修改后的CMakeLists.txt:
cmake_minimum_required(VERSION 3.13) project(pch_extra_def) add_library(lib SHARED lib.cpp) # 移除全局的lib_EXPORTS定义 target_compile_definitions(lib PRIVATE "$<$<NOT:$<BOOL:${MSVC}>>:lib_EXPORTS>") # 仅为lib.cpp添加lib_EXPORTS set_source_files_properties(lib.cpp PROPERTIES COMPILE_DEFINITIONS lib_EXPORTS) target_precompile_headers( lib PUBLIC pch.h ) add_executable(exe exe.cpp) target_precompile_headers( exe REUSE_FROM lib ) target_link_libraries( exe lib ) set_target_properties( exe PROPERTIES COMPILE_WARNING_AS_ERROR ON )
编译后MSVC不会再输出C4651警告,lib的符号正常导出,exe也能正确复用预编译头。
内容的提问来源于stack exchange,提问作者soad

