跨平台构建:平台检测与条件编译
跨平台构建,说白了就是让你的代码能在 Windows、macOS、Linux 上都能跑起来。嗯,这听起来简单,但实际做起来坑不少。我刚开始接触 CMake 时,就因为在路径分隔符上栽过跟头——Windows 用反斜杠,Unix 系用正斜杠,混在一起编译直接报错。
这一章,我们就来聊聊 CMake 里怎么优雅地处理平台差异。
平台检测:CMake 内置变量
CMake 提供了几个内置变量,帮你判断当前构建目标是什么系统。我个人最常用的是这三个:
| 变量名 | 说明 | 典型值 |
|---|---|---|
WIN32 |
Windows 系统(包括 32/64 位) | TRUE / FALSE |
APPLE |
macOS 或 iOS | TRUE / FALSE |
UNIX |
类 Unix 系统(Linux、macOS、BSD 等) | TRUE / FALSE |
注意,APPLE 和 UNIX 在 macOS 上同时为 TRUE。为什么?因为 macOS 内核是 Darwin,属于 Unix 家族。我在项目中遇到过有人用 if(UNIX) 来处理 Linux 特有逻辑,结果在 macOS 上翻车了——你想想看,macOS 没有 /proc 文件系统啊。
核心原则:先判断 APPLE,再判断 UNIX,最后判断 WIN32。这样能避免 macOS 被误归到 Unix 分支。
路径分隔符处理:别再写死斜杠了
路径分隔符是跨平台的第一道坎。Windows 用 \,Unix 用 /。如果你在 CMake 里写死路径,比如 src\core\module.cpp,那在 Linux 上编译直接报错。
CMake 提供了两个解决方案:
- 使用正斜杠
/:CMake 内部会自动转换。Windows 上也能识别正斜杠,这是最省事的做法。 - 使用
file(TO_CMAKE_PATH)和file(TO_NATIVE_PATH):手动转换路径格式。
# 推荐:统一用正斜杠
set(MY_SOURCE_DIR "src/core")
add_executable(myapp ${MY_SOURCE_DIR}/main.cpp)
# 如果需要处理用户输入的 Windows 路径
file(TO_CMAKE_PATH "C:\\Users\\me\\project" PROJ_PATH)
message("CMake 路径: ${PROJ_PATH}") # 输出: C:/Users/me/project
我曾经接手过一个项目,里面全是 ${CMAKE_SOURCE_DIR}\src\ 这种写法。嗯,那代码在 Windows 上能编译,但一到 CI 的 Linux 环境就崩。后来我花了一下午把所有反斜杠改成正斜杠,世界清净了。
我的习惯:所有 CMake 路径统一用正斜杠。Windows 用户放心,CMake 生成 Visual Studio 工程时会自动转成反斜杠。
条件编译:让代码适配平台
条件编译分两层:CMake 层和 C++ 源码层。我们先说 CMake 层。
CMake 层的条件判断
用 if() 指令根据平台添加不同的源文件或编译选项:
if(WIN32)
add_definitions(-DPLATFORM_WINDOWS)
set(PLATFORM_SOURCES
win32/win_main.cpp
win32/win_utils.cpp
)
elseif(APPLE)
add_definitions(-DPLATFORM_MACOS)
set(PLATFORM_SOURCES
macos/mac_main.cpp
macos/mac_utils.cpp
)
elseif(UNIX)
add_definitions(-DPLATFORM_LINUX)
set(PLATFORM_SOURCES
linux/linux_main.cpp
linux/linux_utils.cpp
)
endif()
add_executable(myapp main.cpp ${PLATFORM_SOURCES})
这里有个细节:add_definitions(-DXXX) 相当于在源码里加了一行 #define XXX。你可以在 C++ 代码里用 #ifdef PLATFORM_WINDOWS 来做更细粒度的控制。
C++ 源码层的条件编译
CMake 定义的宏,在 C++ 里直接用:
#include <iostream>
void print_platform() {
#ifdef PLATFORM_WINDOWS
std::cout << "Running on Windows" << std::endl;
#elif defined(PLATFORM_MACOS)
std::cout << "Running on macOS" << std::endl;
#elif defined(PLATFORM_LINUX)
std::cout << "Running on Linux" << std::endl;
#else
std::cout << "Unknown platform" << std::endl;
#endif
}
避坑指南:我曾经在 #ifdef WIN32 里写过 Windows 特有的 API 调用,结果忘了在 CMake 里加 add_definitions(-DWIN32)。编译时那个宏根本没定义,代码直接跳过。记住:CMake 的 WIN32 变量和 C++ 的 WIN32 宏是两回事!
知识体系图
下面这张图总结了跨平台构建的核心逻辑:
实战:一个完整的跨平台示例
我们写一个简单的跨平台程序,打印当前平台信息。先看 CMake 配置:
cmake_minimum_required(VERSION 3.16)
project(CrossPlatformDemo)
# 平台检测与宏定义
if(WIN32)
add_definitions(-DMY_PLATFORM_WINDOWS)
message(STATUS "Target: Windows")
elseif(APPLE)
add_definitions(-DMY_PLATFORM_MACOS)
message(STATUS "Target: macOS")
elseif(UNIX)
add_definitions(-DMY_PLATFORM_LINUX)
message(STATUS "Target: Linux")
endif()
# 添加源文件
add_executable(demo main.cpp)
再看 C++ 源码:
#include <iostream>
int main() {
std::cout << "Hello from ";
#ifdef MY_PLATFORM_WINDOWS
std::cout << "Windows";
#elif defined(MY_PLATFORM_MACOS)
std::cout << "macOS";
#elif defined(MY_PLATFORM_LINUX)
std::cout << "Linux";
#else
std::cout << "Unknown";
#endif
std::cout << " platform!" << std::endl;
return 0;
}
编译运行:
- Windows 上输出:
Hello from Windows platform! - macOS 上输出:
Hello from macOS platform! - Linux 上输出:
Hello from Linux platform!
小技巧:用 message(STATUS "...") 在 CMake 配置阶段打印平台信息,方便调试。我每次写跨平台项目都会在 CMake 开头加几行 message,一眼就能看出当前配置的是哪个平台。
总结
跨平台构建说白了就三件事:
- 检测平台:用
WIN32、APPLE、UNIX三个变量 - 处理路径:统一用正斜杠,别给自己找麻烦
- 条件编译:CMake 层用
if(),C++ 层用#ifdef
嗯,记住这些,你的项目就能在三大平台上愉快地编译了。下一章我们聊聊更高级的——如何管理第三方依赖库。