使用CMake将SDL2/SD2_image静态链接

4

我正在尝试将SDL2作为库添加到我的项目中。我想进行静态链接,但我对C++不熟悉。

1 - 为什么SDL网站推荐尽可能动态链接?

我理解动态库的好处。然而,假设用户已经在他们的系统中安装并准备就绪了所有你需要的库是一个相当大的假设,在我看来。我认为唯一一个使用动态链接听起来像个好注意事项的情况是你正在使用随操作系统/平台提供的已知库。

https://wiki.libsdl.org/Installation

2 - 动态链接似乎可以自动找到(SDL2和SDL2_image)内在依赖项。静态链接则不行。为什么会这样? 这是我的FindSDL2_image.cmake文件。


find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h)
include_directories(${SDL2_IMAGE_INCLUDE_DIR})

# PREFER STATIC LIBRARIES ########
# cmake respects the order of extensions when looking for libraries
SET(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
# ------------------- ########

find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX})
set(SDL2_IMAGE ${SDL2_IMAGE_LIBRARY})


这将静态链接sdl2_image。由于“未定义的符号”,它不能正确链接:
  "_png_set_strip_16", referenced from:
      _IMG_LoadPNG_RW in libSDL2_image.a(IMG_png.o)
  "_png_set_write_fn", referenced from:
      _IMG_SavePNG_RW_libpng in libSDL2_image.a(IMG_png.o)
  "_png_write_png", referenced from:
      _IMG_SavePNG_RW_libpng in libSDL2_image.a(IMG_png.o)

如果我在cmake文件中删除 ### PREFER STATIC LIBRARIES ## 部分,它将链接动态库并且一切都按预期运行。为什么使用静态链接时会解决内部依赖关系而不是动态链接? ----更新---- 通过显式包含其依赖项,我能够静态链接sdl2_image。
find_library(PNGLIB png)
find_library(JPEG jpeg)
find_library(TIFF tiff)
find_library(WEBP webp)
find_library(LZ z)

target_link_libraries(smb ${SDL2} ${PNGLIB} ${JPEG} ${TIFF} ${WEBP} ${SDL2_IMAGE} ${LZ})

然而,这对我来说不是可扩展的。找出这些依赖关系需要一些猜测和搜索。理想情况下,我希望CMAKE能够自动拉取它们。


一切都按预期工作了吗?我认为这可能有点过早了,最好说一切似乎都按预期工作。 :) “ldd”对您的动态可执行文件有何说法?动态链接器是否正在寻找并找到libpng.so.x? - Andrew Atrens
4个回答

4

看起来有几个问题,我会尽力逐步回答这些问题:

为什么SDL网站建议尽可能动态链接?

将应用程序动态链接到库的一个原因是将应用程序与库(在此情况下称为共享库/.so)解耦。您可以更新库而无需重新编译应用程序代码。例如,在完成项目并且客户端正在运行您的应用程序时,我想您不太可能希望重新编译应用程序代码,一旦您正在使用的底层库进行了错误修复。

另一方面,通过将应用程序链接静态链接,您将应用程序与该库(以.lib.a形式)绑定在一起。这意味着库中的任何更改都会导致您重新编译代码。有时候这是必要的,例如,你必须向客户提供应用程序的保证,通常需要确保库不会导致应用程序崩溃。

我有一个简短的示例代码,以更好地理解:


CMakeLists.txt:
cmake_minimum_required (VERSION 3.0)
project(linkageLibrary)

set(STATIC_LIB lib_static)
set(SHARE_LIB lib_share)

set(STATIC_OTHER_LIB lib_otherstatic)
set(SHARE_OTHER_LIB lib_othershare)


add_library(${STATIC_LIB} STATIC ${STATIC_LIB}.cpp)
add_library(${SHARE_LIB} SHARED ${SHARE_LIB}.cpp)

# not yet in usage...
add_library(${STATIC_OTHER_LIB} STATIC ${STATIC_OTHER_LIB}.cpp)
add_library(${SHARE_OTHER_LIB} SHARED ${SHARE_OTHER_LIB}.cpp)

add_executable(${CMAKE_PROJECT_NAME} main.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${${CMAKE_PROJECT_NAME}_SOURCE_DIR})
target_link_libraries(${CMAKE_PROJECT_NAME} ${STATIC_LIB} ${SHARE_LIB})

file(WRITE ${CMAKE_BINARY_DIR}/exchangeShareLibrary.sh "
 echo \"before exchange the static library\"
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME} &&
 mv ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so.bk &&
 cp ${CMAKE_BINARY_DIR}/lib${SHARE_OTHER_LIB}.so ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so &&
 echo \"after the shared library has been changed\" &&
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}")

file(WRITE ${CMAKE_BINARY_DIR}/exchangeStaticLibrary.sh "
 echo \"before exchange the static library\"
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME} &&
 mv ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}.a ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}a.bk &&
 cp ${CMAKE_BINARY_DIR}/lib${STATIC_OTHER_LIB}.a ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}.a &&
 echo \"after the static library has been changed\" &&
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}")

main.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

int main() {
    printStaticLib();
    printShareLib();
    return 0;
}

lib.hpp:

#pragma ONCE

void printStaticLib();
void printShareLib();

lib_static.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printStaticLib() {
    cout << "linkage of lib_static" << endl;
}

lib_share.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printShareLib() {
    cout << "linkage of lib_share" << endl;
}

lib_otherstatic.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printStaticLib() {
    cout << "linkage of the other lib_static with other text" << endl;
}

lib_othershare.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printShareLib() {
    cout << "linkage of the other lib_share with other text" << endl;
}

如果运行生成的脚本,您会发现printShareLib().so被交换后输出不同,但是printStaticLib()没有变化。(尝试不进行清理再次make,如果现在执行./linkageLibrary,您也会得到其他输出printStaticLib()。你能猜出为什么吗?)
对于您的第二个问题: 2-动态链接似乎自动查找内在依赖项(SDL2和SDL2_image)。静态链接不会。为什么会这样?
在不知道您的系统设置的情况下,我猜测您的构建系统无法找到.a静态库。尝试找到它在哪里,并最终将find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image HINTS ${_YOUR_SDL2_INSTALLATION_PATH})链接 现在回到您的问题,为什么建议动态链接SDL2,因为这是库开发人员的决定,您可以在他的README中阅读到。
还请参考此博客,了解如何在CMake中使用SDL2。

至少对于Linux,你可以使用Debian软件包。优点:SDL的安装可以由系统管理员完成,而不是开发人员(他们通常更便宜)。另一个优点是:你可以保持代码整洁,如果你正在使用昂贵的库,你的客户必须购买这个库,而不是开发人员。这就是为什么人们想要动态链接他们的应用程序的原因。通常,只有在必须的情况下,开发人员才会链接他们的应用程序。(如果你链接大型库,你的应用程序将变得越来越大)所以最终问题取决于个人偏好,这就是为什么他们建议动态链接的原因。 - dboy
我猜你已经阅读了一些关于它们的文献,也许这个链接也很有趣。但是我可能忘记提到一个点,即cpp(库)开发(C没有共享库)也与UNIX / Linux系统的开发紧密相关,因此依赖于操作系统的库处理被认为是轻量级和错误少的。别忘了,你只需要在你的发行版中安装apt install libSDL2-dev就可以了。 - dboy
我知道动态链接有其优点,但我在这里谈论的是SDL。SDL主要用于制作游戏。我希望能够将文件发送给我的朋友或上传到互联网上,并让人们玩游戏,而无需担心他们使用哪个Linux发行版或发布复杂的SDL下载教程。一些Linux发行版可能预装了SDL,但我仍不会自动假设所有发行版都有它。 - frankelot
好的,现在我明白了你的用例。无论如何,我猜为什么他们建议动态链接只能由开发人员回答(除了我试图提供给你的明显原因),请查看他们的README,针对你的问题,我会尽力而为。 - dboy
我阅读了您提供的链接,正是因为如此才向Stack Overflow上发了这个问题。该README似乎提供了更多理由来进行静态链接。SDL新增了一层间接,以便 "[...]现在开发人员可以将SDL静态链接,而用户仍然可以替换它!"。他们仍然建议进行静态链接。"...". - frankelot
显示剩余3条评论

2
静态库只是编译后的目标文件集合,没有关于其依赖库的其他信息。您可以使用ar -t &ltstatic_lib>命令查看静态库的内容。因此,如果您需要将可执行文件链接到静态库,则必须提供所有依赖库的名称和路径。
例如:假设有两个静态库A和B,并且A依赖于B。那么如果要将您的exe C链接到A,则链接语句应包含A和B。这样才能正确定义所有符号。
链接器:C ===> A和B
动态库与静态库不同且更加智能。它是ELF格式的,其头文件中包含有关其依赖共享库的信息。这可以通过在其上发出ldd &ltdynamic_lib>命令来查看。此外,动态加载器在运行时知道从标准位置选择依赖库。如果找不到,它会给出一个错误。在您的情况下,您将在ldd输出中找到有关动态库的依赖库的信息,并且可能所有这些库都位于标准位置。这就是您尝试进行动态链接时未发现错误的原因。
以下是一些了解更多信息的有用链接:
https://renenyffenegger.ch/notes/development/dynamic-loader
https://amir.rachum.com/blog/2016/09/17/shared-libraries/

1
当您在CMake中进行find_package时,会在一些CMAKE定义的路径中搜索Findxxx.cmake文件。
该命令有两种模式用于搜索软件包:“模块”模式和“配置”模式。当使用以上简化签名调用该命令时,可用“模块”模式。CMake会在CMAKE_MODULE_PATH后跟CMake安装路径中搜索名为Find.cmake的文件。

(https://cmake.org/cmake/help/latest/command/find_package.html)

所以你需要定义自己的FindSDL2.cmake文件,告诉它库的位置。(https://cmake.org/cmake/help/v3.17/manual/cmake-developer.7.html)
并且你需要告诉find_package去搜索你自己的FindSDL2.cmake。你可以通过传递路径给find_package实现这个功能。
如果你让CMake使用你的文件,那么变量${SDL2_INCLUDE_DIRS}${SDL2_LIBRARIES}将会是你在文件中定义的变量。

谢谢回复!我现在对find_package有了更深的理解。我确定它正在使用/usr/local/Cellar/sdl2/2.0.12_1/lib/cmake/sdl2-config.cmake作为库来配置自己。看起来它同时定义了自己作为STATIC和SHARED库,我更加困惑了。这是那个文件的内容,https://gist.github.com/feresr/0c1c29fb2887a1d75bc4db7785e8231d - frankelot
我所说的“同时定义为静态和共享”是指 -> 这两行代码 add_library(SDL2::SDL2 SHARED IMPORTED) 和 add_library(SDL2::SDL2-static STATIC IMPORTED) 被定义了。 - frankelot
你确定你的CMakeLists正在使用这个.cmake文件吗?因为它不是FindSDL.cmake。 - Aurélien Foucault
我相信,作为一个快速测试,我在sdl2-config.cmake文件中添加了message("this is being run"),并且它被打印到控制台上。 - frankelot

0
这里有几种可能性...
最简单的解释是您在某个地方安装了一个动态的libpng.so.x,而您的动态链接器可以找到它(/usr/lib、/usr/local/lib等),但没有静态的libpng.a。在这种情况下,答案是安装一个静态的libpng.a,并看看cmake的魔法是否能够解决它。
最简单的解释是您的系统上没有libpng.*,并且存在一些动态后期绑定。例如,如果您正在构建的软件也在构建libpng.*,那么后期绑定将允许您成功地将应用程序动态链接到它,即使libpng实际上尚未安装在/usr/lib或/usr/local/lib或任何软件最终放置它的位置。这有点像鸡生蛋蛋生鸡的情况。您可以通过发出“ldd foo”来找出这个问题,其中“foo”是您的应用程序的名称。输出将是动态链接器需要为可执行文件获取的所有内容的杂货清单,libpng应该在列表中,并且应该列出其已解决的路径,如果没有,那么可能会在某个时候调用png_write_png()时导致应用程序启动但随后崩溃。解决此问题的方法是在构建之前安装应用程序所期望的libpng.*库。

希望这能有所帮助 :)


谢谢!我已经检查过了,libpng.a 已经存在于 /usr/local/lib/ 中。请参见我上面更新的问题(如果我显式添加所有依赖项,则我的程序可以正常运行)。 - frankelot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接