27、图形与UI库:使用FetchContent下载Dear ImGui、SFML、SDL2

做C++图形开发,最头疼的事是什么?我个人觉得,不是算法难,也不是API复杂,而是——依赖管理

你想想看,一个图形项目,少说要依赖两三个库。Dear ImGui做调试界面,SFML处理窗口和音频,SDL2做底层输入管理。每个库都有自己的编译方式、版本号、头文件路径。手动下载?太累了。用系统包管理器?版本对不上。用git submodule?更新起来又麻烦。

嗯,这时候FetchContent就派上用场了。它能把依赖管理这件事,变得像写几行CMake代码一样简单。

为什么选这三个库?

我先说说我的选择逻辑。Dear ImGui、SFML、SDL2,这三个库在图形开发里各司其职:

库名 主要用途 为什么用FetchContent
Dear ImGui 即时模式GUI,调试工具 版本迭代快,需要精确控制
SFML 多媒体库,窗口/音频/网络 跨平台编译配置复杂
SDL2 底层图形和输入抽象 很多项目需要特定版本

我在项目中遇到过好几次这样的情况:用系统自带的SDL2,结果版本太老,不支持某个新特性。用FetchContent指定版本,就再也没出过这种问题。

核心思路:一次配置,到处编译

说白了,FetchContent做的事情就是:在CMake配置阶段,自动从GitHub或任意URL下载源码,然后把它当成你项目的一部分来编译。你不需要手动下载、解压、配置路径。一切自动化。

为什么会这么方便?因为FetchContent会把下载的库通过add_subdirectory集成进来,所有目标(target)和头文件路径都自动可用。你只需要在CMakeLists.txt里写几行声明,剩下的交给CMake。

核心原则: 用FetchContent管理图形库,关键在于版本锁定和依赖隔离。不要用latest,永远指定一个你测试过的版本号。

实战:用FetchContent下载Dear ImGui

先来个最简单的。Dear ImGui是一个纯头文件加少量cpp的库,下载和集成非常直接。

include(FetchContent)

FetchContent_Declare(
    imgui
    GIT_REPOSITORY https://github.com/ocornut/imgui.git
    GIT_TAG v1.90.0
)

FetchContent_MakeAvailable(imgui)

# 注意:Dear ImGui需要你自己添加实现文件
target_sources(myapp PRIVATE
    ${imgui_SOURCE_DIR}/imgui.cpp
    ${imgui_SOURCE_DIR}/imgui_draw.cpp
    ${imgui_SOURCE_DIR}/imgui_widgets.cpp
    ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
    ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)

target_include_directories(myapp PRIVATE
    ${imgui_SOURCE_DIR}
    ${imgui_SOURCE_DIR}/backends
)

这里有个坑,我曾经踩过:Dear ImGui本身不提供CMakeLists.txt,所以FetchContent_MakeAvailable不会自动创建任何target。你需要手动把源文件加到你的项目里。嗯,这一点和SFML、SDL2不一样,后两者是完整的CMake项目。

小技巧: 如果你不想手动列出所有cpp文件,可以用file(GLOB)来收集,但要注意CMake的GLOB不会自动检测新文件。我个人习惯显式列出,这样更可控。

实战:用FetchContent下载SFML

SFML就省心多了。它是一个完整的CMake项目,下载后直接可用。

FetchContent_Declare(
    sfml
    GIT_REPOSITORY https://github.com/SFML/SFML.git
    GIT_TAG 2.6.1
)

# 关闭SFML的音频模块,减少编译时间
set(SFML_BUILD_AUDIO OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_NETWORK OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_DOC OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(sfml)

target_link_libraries(myapp PRIVATE sfml-graphics sfml-window sfml-system)

你想想看,如果没有FetchContent,你要手动去GitHub下载SFML的源码,然后编译,再配置路径。现在呢?三行声明,一个MakeAvailable,搞定。

我记得第一次用这个方式集成SFML时,编译通过的那一刻,我差点以为自己在做梦。以前配置SFML至少得花半小时,现在不到一分钟。

注意: SFML的CMake变量必须在FetchContent_MakeAvailable之前设置,否则不会生效。因为MakeAvailable会立即执行下载和配置,之后的变量设置就晚了。

实战:用FetchContent下载SDL2

SDL2的情况稍微特殊一点。它的官方仓库用的是Mercurial,不是Git。不过好在GitHub上有镜像,我们可以直接用。

FetchContent_Declare(
    sdl2
    GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
    GIT_TAG release-2.28.5
)

# SDL2默认会编译很多测试和示例,关掉它们
set(SDL_TEST OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(sdl2)

target_link_libraries(myapp PRIVATE SDL2::SDL2)

这里要注意的是,SDL2的target名称是SDL2::SDL2,不是sdl2。如果你用错了名字,链接时会报找不到target的错误。我曾经在这个问题上卡了半小时,后来才发现是大小写的问题。

整合:一个项目同时使用三个库

现在我们把三个库整合到一个项目里。这是实际开发中最常见的情况。

cmake_minimum_required(VERSION 3.20)
project(MyGraphicsApp)

include(FetchContent)

# ---- Dear ImGui ----
FetchContent_Declare(
    imgui
    GIT_REPOSITORY https://github.com/ocornut/imgui.git
    GIT_TAG v1.90.0
)

# ---- SFML ----
FetchContent_Declare(
    sfml
    GIT_REPOSITORY https://github.com/SFML/SFML.git
    GIT_TAG 2.6.1
)
set(SFML_BUILD_AUDIO OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_NETWORK OFF CACHE BOOL "" FORCE)
set(SFML_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)

# ---- SDL2 ----
FetchContent_Declare(
    sdl2
    GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
    GIT_TAG release-2.28.5
)
set(SDL_TEST OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)

# 一次性下载所有依赖
FetchContent_MakeAvailable(imgui sfml sdl2)

# 创建可执行文件
add_executable(myapp main.cpp)

# 添加Dear ImGui的源文件
target_sources(myapp PRIVATE
    ${imgui_SOURCE_DIR}/imgui.cpp
    ${imgui_SOURCE_DIR}/imgui_draw.cpp
    ${imgui_SOURCE_DIR}/imgui_widgets.cpp
    ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
    ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)

target_include_directories(myapp PRIVATE
    ${imgui_SOURCE_DIR}
    ${imgui_SOURCE_DIR}/backends
)

target_link_libraries(myapp PRIVATE
    sfml-graphics sfml-window sfml-system
    SDL2::SDL2
)

这段代码看起来长,但结构很清晰。三个库的声明放在一起,然后一次性调用FetchContent_MakeAvailable。CMake会自动处理依赖顺序,不会出现A等B、B等A的死锁情况。

性能提示: 第一次运行cmake时,会下载三个库的源码,时间可能比较长。但后续只要版本号不变,CMake会使用缓存,不会再下载。如果你改了版本号,记得删除build/_deps目录,否则CMake可能不会重新下载。

知识体系:FetchContent管理图形库的核心逻辑

下面这张图,是我总结的整个流程。你看一眼就能明白,为什么FetchContent能解决图形库依赖的痛点。

FetchContent 图形库依赖管理流程 1. 声明依赖 FetchContent_Declare 2. 配置选项 set() 变量控制 3. 下载集成 MakeAvailable 4. 在项目中使用 target_link_libraries / target_include 三个图形库 Dear ImGui (GUI) SFML (多媒体) SDL2 (底层抽象) 版本锁定 → 自动下载 → 本地编译 → 无缝集成

从这张图你可以看到,整个流程是线性的:声明 → 配置 → 下载 → 使用。每个库独立声明,但最终都通过MakeAvailable统一集成。这样做的好处是,你可以在一个地方管理所有依赖的版本,不会出现版本冲突。

避坑指南:我踩过的三个坑

最后,分享几个我实际踩过的坑,希望能帮你省点时间。

  • 版本号写错导致编译失败:我曾经把Dear ImGui的版本号写成v1.90,但实际标签是v1.90.0。CMake不会报错,只会下载一个空仓库。排查了半天才发现是版本号的问题。建议每次写版本号前,先去GitHub上确认一下标签名。
  • 忘记关闭测试和示例:SFML和SDL2默认会编译大量测试和示例代码。我第一次集成SDL2时,编译时间从2分钟变成了15分钟。后来才发现是SDL_TEST没关。记住,生产环境一定要关掉这些选项。
  • 缓存导致版本不更新:有一次我改了SDL2的版本号,但重新运行cmake时,它还是用旧版本。后来发现build/_deps目录里有缓存。删除这个目录后重新配置,问题解决。
重要提醒: 如果你在公司内网开发,GitHub可能访问不了。这时候可以用URL代替GIT_REPOSITORY,指向内网的镜像地址。或者用GIT_SHALLOW选项只下载最新版本,减少数据量。

好了,关于用FetchContent下载Dear ImGui、SFML、SDL2,就讲到这里。这三个库的集成方式各有特点,但核心思路是一样的:用CMake的声明式语法,把依赖管理自动化。你只要把版本号锁定好,剩下的交给FetchContent就行。


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