跨平台构建:平台检测与条件编译

跨平台构建,说白了就是让你的代码能在 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

注意,APPLEUNIX 在 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 宏是两回事!

知识体系图

下面这张图总结了跨平台构建的核心逻辑:

跨平台构建核心逻辑 CMakeLists.txt WIN32 APPLE UNIX 添加 Windows 源文件 定义 WIN32 宏 添加 macOS 源文件 定义 APPLE 宏 添加 Linux 源文件 定义 UNIX 宏 最终生成:跨平台可执行文件

实战:一个完整的跨平台示例

我们写一个简单的跨平台程序,打印当前平台信息。先看 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,一眼就能看出当前配置的是哪个平台。

总结

跨平台构建说白了就三件事:

  1. 检测平台:用 WIN32APPLEUNIX 三个变量
  2. 处理路径:统一用正斜杠,别给自己找麻烦
  3. 条件编译:CMake 层用 if(),C++ 层用 #ifdef

嗯,记住这些,你的项目就能在三大平台上愉快地编译了。下一章我们聊聊更高级的——如何管理第三方依赖库。


公众号:蓝海资料掘金营,微信deep3321