使用 clang 从命令行链接 .dylib 库

Linking with .dylib library from the command line using clang

我正在尝试使用库编写 C 程序。该库提供了一个 include/lib/ 目录,分别包含头文件和 .dylib 文件。

我的文件与 include/lib/ 在同一目录中。我正在尝试使用以下命令对其进行编译:

clang -I"./include/" -L"./lib/" -lcsfml-graphics -lcsfml-window test.c

但是,当我转到 运行 我的程序时,出现以下错误:

% ./a.out 
dyld: Library not loaded: @rpath/libcsfml-graphics.2.4.dylib
  Referenced from: ~/src/CSFML-2.4-osx-clang/./a.out
  Reason: image not found
zsh: abort      ./a.out

使用这些库进行编译的正确方法是什么?我更喜欢简单地使用命令行,因为我正在编写一个小程序,而不必设置 Xcode 等

% ls -l lib/
-rwxr-xr-x@ 1  staff   50296 Mar  1  2017 libcsfml-audio.2.4.0.dylib*
lrwxr-xr-x@ 1  staff      26 Mar  1  2017 libcsfml-audio.2.4.dylib@ -> libcsfml-audio.2.4.0.dylib
lrwxr-xr-x@ 1  staff      24 Mar  1  2017 libcsfml-audio.dylib@ -> libcsfml-audio.2.4.dylib
-rwxr-xr-x@ 1  staff  163680 Mar  1  2017 libcsfml-graphics.2.4.0.dylib*
lrwxr-xr-x@ 1  staff      29 Mar  1  2017 libcsfml-graphics.2.4.dylib@ -> libcsfml-graphics.2.4.0.dylib
lrwxr-xr-x@ 1  staff      27 Mar  1  2017 libcsfml-graphics.dylib@ -> libcsfml-graphics.2.4.dylib
-rwxr-xr-x@ 1  staff   67272 Mar  1  2017 libcsfml-network.2.4.0.dylib*
lrwxr-xr-x@ 1  staff      28 Mar  1  2017 libcsfml-network.2.4.dylib@ -> libcsfml-network.2.4.0.dylib
lrwxr-xr-x@ 1  staff      26 Mar  1  2017 libcsfml-network.dylib@ -> libcsfml-network.2.4.dylib

您应该检查环境变量 DYLD_LIBRARY_PATH。它应该包含 libcsfml-graphics.2.4.dylib 的路径。另一种解决方案可能是将此路径添加到您的 PATH 变量。

您可以使用 otool -L 检查二进制文件的路径并使用 install_name_tool -change 修改它们。不过,理想情况下,您应该使用 -R when linking/compiling.

构建二进制文件以包含其依赖项的正确 rpath

似乎在使用 clang -I"./include/" -L"./lib/" -lcsfml-graphics -lcsfml-window -Wl,-rpath,"@executable_path/lib" test.c 修复了 rpath 之后,您仍然缺少 SFML 库。我刚刚检查过 CSFML 依赖于 SFML,并且根据您的 ls -l lib/ 清单,lib/ 中显然没有 SFML。我的猜测是,在修复 rpath 之后,您可能没有注意到不存在的依赖项已更改为 @rpath/libsfml-graphics.2.4.dylib 而不是 @rpath/libcsfml-graphics.2.4.dylib。请下载 SFML 库并将它们放入 lib/.

现在回答你的问题,如何从命令行在 macOS 上构建。您的构建步骤是正确的,所以我想困难的部分是 dyld 如何搜索依赖项,而不是它们如何与 ld 链接。

一个简短的理论。

二进制文件(可执行文件或动态库)引用其依赖项的方式有 4 种:

  • 通过绝对路径或相对路径(后者相对于您的工作目录);
  • by @executable_path,扩展到作为依赖树根的可执行文件的路径(如果你有递归依赖);
  • by @loader_path,扩展到搜索依赖的可执行文件或库的路径,即它是依赖树中直接父级的路径;
  • @rpath,它依次被二进制文件中的每个 rpath 替换,这是最灵活的方式,因为您可以通过多个 rpath 在多个目录中进行搜索。

现在的问题是如何确保正确引用依赖项。正如@mattmilten 指出的那样,有两种方法:

  • 进行构建 system/build script/manual 构建命令确保引用正确;
  • 使用 install_name_tool 更正损坏的引用。

在构建过程中自动执行。

为了使第一种方法起作用,您需要确保依赖库的标识名称 是正确的。假设您将二进制文件链接到某个库 libA.dylib。 libA.dylib 的标识名称是链接器 (ld) 在构建二进制文件时将使用的默认引用。您可以通过查看 otool -L libA.dylib 的第一行(或者在 otool -l libA.dylib 的 LC_ID_DYLIB 部分找到它)。在 libcsfml-graphics.2.4.dylib 的情况下,它是 @rpath/libcsfml-graphics.2.4.dylib 并且它在链接时被传递给 a.out ,这就是为什么当 dyld 无法满足它时您会在错误消息中看到它。

设置正确的标识名是 libA.dylib 作者的责任。它是根据他们对 libA.dylib 放置位置的期望(使用 @rpath 是一个不错的选择)以及他们的版本控制方案(如果 CSFML 版本 2.4.0 可以替换为 2.4.x 而不会失去二进制兼容性)。如果您自己构建 libA.dylib,您可以使用 -install_name 参数设置它(例如 clang -Wl,-dylib -Wl,-install_name,@executable_path/libA.dylib -o libA.dylib liba.c)。如果它是第 3 方库,您可以使用 install_name_tool -id @rpath/libA.dylib libA.dylib.

进行更改

据我所知,标识名仅在链接期间使用,而不在加载二进制文件时使用,因此如果您更喜欢使用 install_name_tool 修复不正确引用的第二种方法,则可以忽略它。

手动修复。

用 install_name_tool 修复引用很简单但很乏味。当有很多不正确的引用时,您可能需要一个脚本。您所需要的只是以下一组命令(binary 占位符显然应该替换为实际的二进制名称):

  • install_name_tool -change @executable_path/libA.dylib @rpath/libA.dylib binary 更改引用 @executable_path/libA.dylib -> @rpath/libA.dylib;
  • install_name_tool -add_rpath @executable_path/lib binary 添加 @executable_path/lib 到 rpaths;
  • install_name_tool -delete_rpath @executable_path/lib binary 从 rpaths 中删除 @executable_path/lib
  • otool -l binary 检查现有的 rpaths(只需查看 LC_RPATH 部分)。