【问题标题】:Make a C++ SDL2 standalone and cross-platform-development with CMake使用 CMake 进行 C++ SDL2 独立和跨平台开发
【发布时间】:2021-09-10 22:32:30
【问题描述】:

我的目标是使用 SDL2 构建一个 C++ 应用程序,该应用程序可以在 Linux 和可能未安装 SDL2 的 Windows 上运行。 (我知道有很多关于它的帖子,我稍后会来)

所以我正在浏览我的小项目结构,我记得使用 CMake 来提高构建效率。所以我记录了很多自己并得出这个结构:

- app (contains the main function)
  |- main.cpp
  |- CMakeLists.txt
- build
- libs (contains externals libraries such as: "spdlog", "googletest")
- src
  |- all the source files
  |- CMakeLists.txt
- tests
  |- tests files
  |- CMakeLists.txt
CMakeLists.txt

结构不是本次讨论的重点。

现在我的项目结构已经完成,我专注于构建文件。

感谢很多帖子,我明白我应该通过静态链接。 (我知道诸如重新编译静态库、没有自动最后更新、更大的文件等缺点。)。我想将我的程序作为“文件夹”分发,所以我排除了(包)安装程序的解决方案。 我的程序必须能够使用他自己的资源(文件夹)运行。另外,请记住 SDL2 带有允许静态链接的“zlib 许可证”。

所以我写了我的 src/CMakeLists.txt

set(PROJ_LIB_NAME "projectlib")

configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/config/cmake_config.hpp.in 
    ${CMAKE_CURRENT_SOURCE_DIR}/config/cmake_config.hpp
)

set(PROJ_LIB_SOURCES
    core.cpp
    utils/logs_utils.cpp
    gui/gui.cpp
)

add_library(${PROJ_LIB_NAME} STATIC ${PROJ_LIB_SOURCES})
target_include_directories(${PROJ_LIB_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

### ====================================
### LIBRARIES
### ====================================

### spdlog
### ====================================
if(NOT TARGET spdlog)
    # Stand-alone build
    find_package(spdlog REQUIRED)
endif()
target_link_libraries(${PROJ_LIB_NAME} PRIVATE spdlog::spdlog)


### SDL2
### ====================================
find_package(SDL2 REQUIRED)
target_include_directories(${PROJ_LIB_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(${PROJ_LIB_NAME} PRIVATE -static ${SDL2_LIBRARIES})

请注意最后一行的-static

还有我的 CMakeLists.txt(根):

cmake_minimum_required(VERSION 3.16.3 FATAL_ERROR)
enable_testing()

set(PROJ_NAME "progr")
set(PROJ_VERSION "1.0.0")

# set the project name
project(${PROJ_NAME} VERSION ${PROJ_VERSION})

# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# specify compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -Wpedantic")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")

add_subdirectory(${CMAKE_SOURCE_DIR}/libs/googletest-1.11.0)
add_subdirectory(${CMAKE_SOURCE_DIR}/libs/spdlog-1.8.5)
add_subdirectory(${CMAKE_SOURCE_DIR}/src)
# add_subdirectory(${CMAKE_SOURCE_DIR}/tests)
# add_subdirectory(${CMAKE_SOURCE_DIR}/app)

最后,app/CMakeLists.txt

set(PROJ_EXE_NAME "main")

add_executable(${PROJ_EXE_NAME} main.cpp)
target_link_libraries(${PROJ_EXE_NAME} PUBLIC projectlib)

看起来不错,但不起作用。如果我取消注释add_subdirectory(${CMAKE_SOURCE_DIR}/app) 来构建我的应用程序,我会得到一堆来自“/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a”、“../libs/spdlog-1.8”的未定义引用.5/libspdlogd.a", "/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/libSDL2.a"...


到目前为止,我为使我的应用程序独立可移植所做的尝试:

  • 下载 SDL2 源代码并将其放在 libs/ 中,告诉 CMake 将其视为子目录 -> 这很愚蠢,因为源代码旨在在您的计算机上安装 SDL,而不是用作它由您的项目...(按照 SDL 安装指南,您可以)
  • 正在寻找 findSDL2.cmake 文件 -> 我真的需要那些难以理解的文件吗?此外,它对 SDL1 很好,但对 SDL2 不再适用了......
  • 2 天以来在 CMake 中使用了很多不同的参数...
  • 还有很多我暂时不记得了...

没有一个解决方案能得到所有人的称赞。

我不明白为什么即使在没有安装 SDL2 的机器上也没有关于如何实现制作 C++ SDL2 便携应用程序目标的教程......每个人似乎都同意肯定会安装像 Steam 这样的应用程序并且由他们来管理 SDL。那么那些不通过 Steam 的游戏呢?但话又说回来,我想知道这是否是个好主意。作为一名 JS 开发人员,当我构建一个具有 NPM 依赖项的应用程序时,我会阻止我的包版本,以便所有开发人员都使用相同的工具并且我的生产环境是稳定的。我不明白这个动态库逻辑,所以我很乐意得到解释:)


编辑:忘了提到我的项目库(src/CMakeLists.txt)构建良好,当我尝试将它与我的主要功能(app/CMakeLists.txt)链接时一切都出错了


编辑 2: 有一个有效的静态链接,但必须下载其他一些依赖项......到目前为止我做了什么:

  • 下载了 SDL 源代码并将其放入我的 /libs 文件夹 (https://www.libsdl.org/download-2.0.php)(无需在其中创建构建目录并运行“make”或“configure”命令。CMakeLists 将为你处理这个。另外,不要'make install')
  • 在构建源代码之前修改我的根 CMakeLists.txt 以添加 add_subdirectory(${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14)
  • 修改我的 src/CMakeLists.txt 以注释所有 SDL find_package 的东西(等等...)并只添加链接的库。请参阅:
### SDL2
### ====================================
## Comments
#set(SDL2_DIR ${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14)
#find_package(SDL2 REQUIRED)
#target_include_directories(${PROJ_LIB_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
#target_link_libraries(${PROJ_LIB_NAME} PRIVATE -static ${SDL2_LIBRARIES})
## end Comments

## the only needed link to SDL2
target_link_libraries(${PROJ_LIB_NAME} PRIVATE SDL2main SDL2-static)
  • 之后,在 SDL 初始化时运行我的应用程序给我一些错误,如“没有可用的视频设备”,但我的应用程序正在构建!
  • 事实证明,当您执行sudo apt install libsdl2-dev我们想要避免的事情)时,我需要安装更多的依赖项(我猜)。您可以在此处找到所有依赖项:https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md#build-dependencies
  • 我已经安装了依赖项并且... 一切正常!

我想我的新问题现在是“如果我尝试在没有这些依赖项的计算机上运行我的应用程序怎么办?”。问这个是因为我设法在安装它们之前编译和运行我的应用程序。我得出结论,我的可执行文件中包含 SDL 代码,但没有依赖项代码。

我仍然想知道为什么静态链接总是如此妖魔化,而对我来说它允许控制依赖项的版本。我同意“错误修复”的说法,但这些新版本可能与您的应用程序不兼容。在这种情况下,您应该向您的用户解释,当您的代码没有导致回归时,您正在努力发布修复程序......

【问题讨论】:

    标签: c++ cmake cross-platform sdl sdl-2


    【解决方案1】:

    所有这些都假定 CMake 的最新版本 - 如果这不适合您,您可能需要安装更新的版本。

    所以,首先,您要编译 SDL2。在 Linux 上,对于最新版本,这意味着需要像这样下载和构建源代码:

    wget https://www.libsdl.org/release/SDL2-2.0.14.tar.gz
    tar -xvf SDL2-2.0.14.tar.gz
    cd SDL2-2.0.14
    mkdir build
    cd build
    cmake .. -DCMAKE_INSTALL_PREFIX=~/SDL2
    make -j8
    make install
    

    这意味着您现在在您的机器上的 ~/SDL2 文件夹中拥有一个 SDL2 的构建副本(您可能想要更改此位置)。 CMake 有一个非常老式的内置 FindSDL 模块,但要使用它,请将以下内容添加到您的 CMakeLists.txt

    find_package(SDL 2 REQUIRED)
    # When building an application:
    include_directories(${SDL_INCLUDE})
    target_link_libraries(myapplication SDL::SDL)
    

    然后,当您在命令行调用来构建您的项目时,请执行以下操作:

    cmake .. -DSDL_LIBRARY=~/SDL2/lib -DSDL_INCLUDE_DIR=~/SDL2/include
    

    对于 Windows,它与 CMake 的过程大致相同,除了如果您使用的是 VS2019,则通过 GUI 使用 CMake,因此在其中设置参数而不是在命令行上。如果您从开始菜单打开开发人员命令提示符,则可以在命令行上执行此操作(这是必需的,以便为 CMake 设置编译器变量以查找)。

    就其他库而言 - 在 Linux 上,您将始终需要依赖系统提供的 C 标准库 (libc)(除非您使用 musl)。通常,这是向前兼容但不向后兼容的,因此许多开发人员在非常旧的操作系统上编译他们的代码,以确保它“无处不在”运行。您还需要在 libstdc++ 中进行静态链接。在 Windows 上,存在可再发行包(例如“Visual C++ Redistributable for Visual Studio 2019”),它们通常随您的应用程序一起分发或假定已安装。

    【讨论】:

    • 非常感谢您抽出宝贵时间回答我的问题,但我认为我们彼此并不了解......我设法用 SDL2 编译了我的项目(实际上是我的库)。为此,我通过了 Ubuntu 包管理器。我运行了 libsdl2-dev 命令。我现在要做的是确保在分发我的应用程序时,我的非 SDL 用户不必在启动我的应用程序之前安装它(在 Linux 和 WIndows 上)。所以我想发布我的应用程序,其中包含 SDL2。但仍然感谢您的回答,我也会静态链接 libstdc++。
    • 我完全明白这一点。 Ubuntu 没有在 libsdl2-dev 包中提供 SDL2 的静态副本(您可以通过运行 pkg-config sdl2 --static 来检查),而只提供共享库,因此如果您希望您的应用程序可以在任何地方运行,您需要自己构建它。
    • 哦,好吧,我不知道。我会尝试您的解决方案,但仍然很高兴知道我的包不包含静态版本!
    【解决方案2】:

    回答标题中的问题,忽略问题中的错误。我将建议一个不同的解决方案。


    在 Windows(假设 MinGW)上,这很容易。静态链接并不重要:如果您不这样做,您只需将所有必要的 dll 与您的可执行文件一起提供。

    所需dll列表确定如下:

    • 确保您的 MinGW 和库附带的 dll 不与 C:\Windows(包括子目录)中的那些重叠。如果您看到重叠,请删除 C:\Windows 中的匹配 dll。一些蹩脚的安装程序喜欢在其中放置自定义 dll,这往往会导致问题。

    • 打开一个shell,并在其中添加 MinGW的bin/目录到PATH

    • 使用ntldd -R <filename.exe> 获取dll 列表。 MinGW 的bin/ 中的那些你必须发货。


    在 Linux 上,有几个突出的解决方案:AppimageFlatpakSnap

    我对此没有太多经验,但这是我一直在做的事情:

    • 确保您运行的是您希望支持的最旧的 Linux 版本(我使用的是 Ubuntu 18.04)。你可以在 Docker 下运行它。
    • 从源代码构建 SDL2,不要从包管理器中获取它。至少在 Ubuntu 上,我听说打包后的版本不会在运行时动态查找可用的依赖项,从而降低了可移植性。
    • 使用ldd 确定依赖项列表。哪些需要运送必须通过实验确定。首先将它们全部复制到当前目录。
    • 在所有这些库和您的可执行文件上使用patchelf --set-rpath '$ORIGIN' <filename>。 ('s 很重要,您不希望 $ORIGIN 被扩展为 shell 变量。)
    • 将整个目录复制到不同的系统(或一堆),看看它是否运行。您必须删除您复制的一些共享库,直到它开始工作。最后我只得到了libstdc++.so.6libgcc_s.so.1 和我使用的库。

    或者,您可以使用设置LD_LIBRARY_PATH 而不是patchelf 的启动器脚本。

    【讨论】:

    • 感谢您的回答和所有提示!对我非常有用 :) 确实,为 Windows 版本提供 .dll 并没有那么严格。对于 Linux,这些工具在我看来是一个很好的解决方案,但最好的解决方案必须是静态链接,不是吗?我的意思是,它是为这种特定用途而设计的,不是吗?
    • @spMaax 我不知道,但我不断听到“Linux 上的静态链接被破坏”等等。所以我不确定。
    猜你喜欢
    • 1970-01-01
    • 2012-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-28
    • 1970-01-01
    • 2012-01-25
    相关资源
    最近更新 更多