使用 CMake 静态链接 SDL2/SD2_image

Linking SDL2/SD2_image statically with CMake

我正在尝试将 SDL2 作为库添加到我的项目中。我想 link 它是静态的。我是 c++ 的新手。

1 - 为什么 SDL 网站建议尽可能动态地 linking?

我了解动态库的好处。然而,假设用户已经安装了你需要的所有库并准备好进入他们的系统是一个相当大的假设 IMO。 linking 动态对我来说是个好主意的唯一情况是你使用的是 OS/platform 附带的众所周知的库。 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})

这个link是静态的sdl2_image。它不正确 link 因为 Undefined symbols:

  "_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 ## 部分。它 link 是动态的,一切都按预期工作。为什么当动态 linking 时内在依赖关系得到解决,而当 linking 静态时却没有?

----更新----

我能够 link 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 自动提取这些内容。

当您在 CMake 中执行 find_package 时,将在某些 CMAKE 定义的路径中搜索 Findxxx.cmake 文件。

The command has two modes by which it searches for packages: “Module” mode and “Config” mode. Module mode is available when the command is invoked with the above reduced signature. CMake searches for a file called Find.cmake in the CMAKE_MODULE_PATH followed by the CMake installation.

(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} 将是您在文件中定义的变量。

这里有几种可能性..

最简单的解释是,您在动态 link 用户可以找到的地方安装了动态 libpng.so.x(/usr/lib、/usr/local/lib 等),但是没有静态 libpng.a。在这种情况下,答案是安装静态 libpng.a 并查看 cmake 的魔法是否可以解决它。

下一个最简单的解释是您的系统上没有 libpng.*,并且正在进行一些动态后期绑定。例如,如果您正在构建的软件也在构建 libpng。* 在这种情况下,后期绑定将允许您成功地动态 link 您的应用程序,即使 libpng 实际上尚未安装在 /usr/lib 或 /usr/local/lib 或您正在构建的软件最终将放置它的任何位置。这是一种先有鸡还是先有蛋的情况。您可以通过发出 "ldd foo" 来解决这个问题,其中 "foo" 是您的应用程序的名称。输出将是动态 linker 需要为你的可执行文件获取的所有东西的杂货清单,libpng 应该在列表中并且应该有一个为它列出的解析路径,如果没有,你的应用程序可能如果在某个时候调用 png_write_png() ,则可能会启动但随后会崩溃。解决方法是在构建之前安装您的应用程序期望的 libpng.* 库。

希望对您有所帮助:)

静态库仅仅是 collection 个已编译的 object 个文件。它没有关于其依赖库的任何附加信息。您可以使用 ar -t <static_lib> 命令查看静态库的内容。因此,如果您需要 link 您的可执行文件到静态库,您必须提供其所有依赖库的名称和路径。

例如:考虑两个静态库,A 和 B 并假设 A 依赖于 B。然后如果想要你的 exe C 到 link 到 A,那么你的 link 语句应该同时包含A 和 B。然后只有所有符号才能正确定义。
链接器:C ===> A 和 B

动态库与静态库不同,而且更智能。它是 ELF 格式,它的 headers 有关于它依赖的共享库的信息。这可以通过在其上发出 ldd <dynamic_lib> 命令来查看。动态加载器也知道在运行时从标准位置选择依赖库。如果找不到,它会报错。在您的情况下,您会在 ldd 输出中找到有关动态库的依赖库的信息,并且大概所有这些库都位于标准位置。这就是您在尝试动态 link.

时没有发现错误的原因

这里有一些有用的 link 可以了解更多信息,
https://renenyffenegger.ch/notes/development/dynamic-loader
https://amir.rachum.com/blog/2016/09/17/shared-libraries/

好像有几个问题,我会尽量一步一步回答问题:

Why does the SDL website recommend linking dynamically whenever possible

link 动态地针对库应用程序的原因之一是将应用程序与库分离(在这种情况下称为共享库/.so)。您可以更新您的库,而无需重新编译您的应用程序代码。例如。如果你已经完成了你的项目,并且你的客户有你的应用程序 运行ning,我想,一旦你正在使用的底层库有错误修复,你就不太可能想要重新编译你的应用程序代码.

另一方面,通过 link 应用程序 statically,您将应用程序与该库绑定(.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 - Linking dynamically seems to automatically find the intrinsic dependencies of (SDL2 and SDL2_image). Linking statically does not. Why is this the case?

在不知道您的系统设置的情况下,我猜想您的构建系统无法找到 .a 静态库。试着找到它在哪里,最终把find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image HINTS ${_YOUR_SDL2_INSTALLATION_PATH})link

现在回到你的问题,为什么 SDL2 建议动态 linked,这是库开发人员的决定,你可以在他的 README

中读到

另请参阅此blog 如何将 SDL2 与 CMake 一起使用