c++ - 使用 CMake 进行 C++ SDL2 独立和跨平台开发
问题描述
我的目标是用 SDL2 构建一个 C++ 应用程序,它可以在可能没有安装 SDL2的Linux 和 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”)
add_subdirectory(${CMAKE_SOURCE_DIR}/libs/SDL2-2.0.14)
在构建源代码之前修改我的根 CMakeLists.txt 以添加- 修改我的 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 代码,但没有依赖项代码。
我仍然想知道为什么静态链接总是如此妖魔化,而对我来说它允许控制依赖项的版本。我同意“错误修复”的说法,但这些新版本可能与您的应用程序不兼容。在这种情况下,您应该向您的用户解释,当您的代码没有导致回归时,您正在非常努力地发布修复程序......
解决方案
回答标题中的问题,忽略问题中的错误。我将建议一个不同的解决方案。
在 Windows(假设 MinGW)上,这很容易。静态链接并不重要:如果您不这样做,您只需将所有必要的 dll 与您的可执行文件一起发送。
所需dll列表确定如下:
确保您的 MinGW 和库附带的 dll 不与
C:\Windows
(包括子目录)中的那些重叠。如果您看到重叠,请删除C:\Windows
. 一些蹩脚的安装程序喜欢将自定义 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>
在所有这些库和您的可执行文件上使用。('
s 很重要,您不想$ORIGIN
被扩展为 shell 变量。)- 将整个目录复制到不同的系统(或一堆),看看它是否运行。您必须删除您复制的一些共享库,直到它开始工作。我最终得到了
libstdc++.so.6
,libgcc_s.so.1
和我使用的库。
或者,您可以使用设置LD_LIBRARY_PATH
而不是patchelf
.
推荐阅读
- php - 如何在 laravel 本地范围内使用 max?
- python - 执行包含自定义转换器的代码时出现问题?
- react-native - react-native state 和 flatlist 组件问题
- node.js - 通过 nodeJS 中的 ssh 使用隧道 ssh 连接到 Docker 中的远程服务器 mongoDB,AuthenticationFailed
- python - 如何从 mel 频谱图转换 wav(音频)文件?
- linux - 禁用除 root 访问的隐藏命令之外的所有内容
- html - 'verifyForm' 对象没有属性 'reception'
- node.js - 谷歌视觉 API 两列图像文本提取变得一团糟
- php - 在Laravel php中将两个json数组合并为一个
- javascript - 移动设备 ASP.NET MVC Core 3.1 未加载修改后的 js (javascript)