使用具有依赖关系的动态库链接

89
考虑以下情景:
共享库 libA.so ,没有依赖关系。
共享库 libB.so,以 libA.so 为其依赖项。
我想编译一个链接到 libB 的二进制文件。 我应该只链接二进制文件和 libB 还是同时链接 libA?
是否有一种方法只与直接依赖项链接,让未解决的符号从依赖项中的运行时解析?
我担心库 libB 的实现将来可能会发生更改,引入其他依赖项(例如 libC、libD、libE)。我会遇到问题吗?
换句话说: libA 文件:a.cpp a.h libB 文件:b.cpp b.h 主程序文件:main.cpp 当然,b.cpp 包括 a.h,而 main.cpp 包括 b.h。
编译命令:
g++ -fPIC a.cpp -c
g++ -shared -o libA.so a.o

g++ -fPIC b.cpp -c -I.
g++ -shared -o libB.so b.o -L. -lA

我应该使用下面哪个选项?

g++ main.cpp -o main -I. -L. -lB
或者
g++ main.cpp -o main -I. -L. -lB -lA

我无法使用第一个选项。链接器报告来自库libA的未解决符号,但这对我有些奇怪。

非常感谢。

-- 更新的评论:

当我链接二进制文件时,链接器将尝试从主库和libB中解析所有符号。然而,libB具有来自libA的未定义符号。这就是为什么链接器会抱怨的原因。

这就是为什么我需要与libA一起链接的原因。 不过我发现了一种忽略共享库中未解决符号的方法。 看起来我应该使用以下命令行来做到这一点:

g++ main.cpp -o main -I. -L. -lB -Wl,-unresolved-symbols=ignore-in-shared-libs

看起来仍然可以使用-rpath选项。但我需要更好地理解它。

当使用-Wl,-unresolved-symbols = ignore-in-shared-libs选项时,是否有任何可能的陷阱?

- 评论已更新2:

不应该使用-rpath进行此目的。它用于强制在给定目录中找到库。使用-unresolved-symbol方法看起来更好。

再次感谢。


以下是可以运行的最小示例,供那些想要测试这种东西的人使用:https://github.com/cirosantilli/cpp-cheat/tree/b9d026ae960d92e7a09bd34b6406d39429930d63/shared-library/lib-lib-dependency - Ciro Santilli OurBigBook.com
4个回答

49

看起来你已经完成了大部分工作。恭喜你的调查工作。让我们看看我能否帮助你理清其中的“原因”。

这就是链接器在做的事情。当你链接可执行文件(如上面的'main')时,它有一些未解决的符号(函数和其他东西)。它会查看后面的库列表,尝试解决这些未解决的符号。在此过程中,它发现一些符号由libB.so提供,所以它会记录这些符号现在由该库解决。

然而,它还发现其中一些符号使用了尚未在你的可执行文件中解决的其他符号,因此它现在需要解决这些符号。如果没有链接到libA.so,你的应用程序将不完整。一旦链接到libA.so,所有符号均得到解决,链接完成。

正如你所看到的,使用-unresolved-symbols-in-shared-libs并不能解决问题。它只是推迟了问题,使这些符号在运行时被解决。这就是-rpath的作用:指定要在运行时搜索的库。如果这些符号在那时仍无法解决,你的应用程序将无法启动。

解决库依赖关系并不容易,因为一个符号可以由多个库提供,并且链接到其中任何一个都可以满足。

这里还有另一种描述过程的方法: 为什么链接库的顺序有时会导致GCC出错?


2
我知道这是很久以前的事了,我忘记将其标记为已解决。非常感谢你的回答。 - Marcus
谢谢大家提供这个惊人的问题和恰当的答案!我几乎有完全相同的情况。我正在尝试将openCV共享对象包装在自定义共享对象中。假设C.so是我的自定义so,而CV.so是一些openCV so。我已经成功地将C.so与CV.so链接起来,并且我想将main.cpp(你懂的!)链接到C.so,但是除了使用C.so中的对象之外,main.cpp还使用了一个在CV.so中定义的对象'cv :: Mat'。在这种情况下,为了使main.cpp运行,我需要同时链接C.so和CV.so吗?如果有人能够解释一下,我会非常感激。谢谢! :) - Salar Khan
2
为什么链接器在生成libB.so时不解析所有所需的libA.so符号? 主应用程序不应该链接到libA,因为它应该是透明的,例如通过libB间接屏蔽? - Wilson
这是不正确的。链接器除了通知用户存在不完整的依赖链外,不使用libA.so。如果不提供libA.so而是传递-unresolved-symbols=ignore-in-shared-libs,则会得到完全相同的可执行文件。 - listerreg

24

对于仅与直接依赖项进行动态链接的情况,您可以使用-Wl,--as-needed,并在-Wl,--as-needed之后添加库:

gcc main.c -o main -I. -L. -Wl,--as-needed -lB -lA

要检查直接依赖关系,您应该使用readelf而不是ldd,因为ldd还显示了间接依赖关系。

$ readelf -d main | grep library
0x0000000000000001 (NEEDED)             Shared library: [libB.so]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

ldd还显示间接依赖项:

$ LD_LIBRARY_PATH=. ldd ./main
linux-vdso.so.1 (0x00007fff13717000)
libB.so => ./libB.so (0x00007fb6738ed000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb6734ea000)
libA.so => ./libA.so (0x00007fb6732e8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb673af0000)

如果您使用的是 cmake,可以添加以下行来仅包含直接依赖项:
set(CMAKE_EXE_LINKER_FLAGS    "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}")

抱歉,我将您的示例制作为C程序而不是C ++。因此,我使用了gcc编译器。但它也可以与g ++一起使用。 - user1047271
1
这是非常有用的信息。我不知道可以使用readelf而不是ldd。非常感谢。 - Marcus

1

另一个选择是使用 libtool

如果您将g++调用更改为libtool --mode=compile g++来编译源代码,然后使用libtool --mode=link g++libB创建应用程序,则libA将自动链接。


0

这是一篇有趣的帖子 - 我也曾经为此苦恼,但我认为你在这里错过了一个重点..

这个想法是这样的,对吧?

main.cpp =(depends)=> libB.so =(depends)=> libA.so

让我们进一步考虑...

  • 在a.cpp中(仅限于那里),你定义了一个类/变量,我们称之为"symA"
  • 在b.cpp中(仅限于那里),你定义了一个类/变量,我们称之为"symB"
  • symB使用了symA
  • main.cpp使用了symB

现在,libB.so和libA.so已经按照你上面描述的编译完成。之后,你的第一个选项应该可行,即:

g++ main.cpp -o main -I. -L. -lB

我猜你的问题源于

在main.cpp中你也引用了symA

我的理解是对的吗?

如果你在代码中使用一个符号,那么这个符号必须在.so文件中找到。

交叉引用共享库(即创建API)的整个思想是隐藏较深层次的符号(类似剥洋葱),不使用它们。也就是说,在main.cpp中不要引用symA,而只引用symB(并让symB引用symA)。


1
你误解了。显然,你需要解决立即所需的符号。问题与你的依赖项可能需要的符号有关。在开发供第三方使用的库时,通常需要提供实现功能的库用户。你使用存根进行开发,没有任何依赖关系,集成者将开发一个库来替换存根,然后将所有内容链接在一起。加载器将不得不解决所有依赖项。但是,你的编译脚本(以及你的用户)必须考虑到这一点。 - Marcus

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