使用CMake进行C++SDL2独立和跨平台开发



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

所以我正在浏览我的小项目结构,我记得我使用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源代码并将其放在my/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-依赖项
  • 我已经安装了依赖项一切都很好

我想我的新问题现在是">如果我试图在没有这些依赖关系的计算机上运行我的应用程序,该怎么办";。之所以这么问,是因为我设法在安装它们之前编译并运行了我的应用程序。我得出的结论是,我的可执行文件中有SDL代码,但没有依赖关系代码。

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

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


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

所需dll的列表确定如下:

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

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

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


在Linux上,有几个突出的解决方案:Appimage、Flatpak、Snap。

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

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

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

所有这些都假设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,因此在GUI中而不是在命令行上设置参数。如果从开始菜单打开开发人员命令提示符,则可以在命令行上执行此操作(这是必需的,以便为CMake设置编译器变量以查找)。

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

您可以使用我在项目中使用的方法,我使用SDL2和SLD_Image,并通过FetchContent将它们获取到其repo上的特定TAG

cmake_minimum_required(VERSION 3.24)
project(sdl_test)
include(FetchContent)
Set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
SDL2
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-2.26.3
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(SDL2)
FetchContent_Declare(
SDL2_image
GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git
GIT_TAG release-2.6.3
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
set(SDL2IMAGE_INSTALL OFF)
set(BUILD_SHARED_LIBS FALSE)
FetchContent_MakeAvailable(SDL2_image)
add_executable(sdl_test main.cpp)
target_link_libraries(sdl_test SDL2::SDL2main SDL2::SDL2-static SDL2_image::SDL2_image-static)

有了这个,你将拥有它的静态和跨平台工作。

Include目录将照常工作。

#include <SDL.h>

记住要有这种格式的主

int main(int, char *[]) {
....
}

最新更新