与依赖项链接动态库


79

请考虑以下情形:

  • 共享库libA.so,没有依赖项。
  • 共享库libB.so,依赖项为libA.so。

我想编译一个与libB链接的二进制文件。我应该仅将二进制文件与libB链接,还是与libA链接?

有什么方法只能与直接依赖项链接,从而使依赖项中的未解析符号解析为运行时吗?

我担心库libB的实现将来可能会改变,引入了其他依赖项(例如libC,libD,libE)。我会遇到问题吗?

换一种说法:

  • libA文件:a.cpp啊
  • libB文件:b.cpp bh
  • 主程序文件:main.cpp

当然,b.cpp包含ah,main.cpp包含bh

编译命令:

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中有未解析的符号。但这对我来说有点奇怪。

非常感谢。

-更新的评论:

当我链接二进制文件时,链接器将尝试解析main和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方法看起来好多了。

再次感谢。


这里是为那些希望测试这种东西出一个可运行的小例子:github.com/cirosantilli/cpp-cheat/tree/...
西罗桑蒂利郝海东冠状病六四事件法轮功

Answers:


42

看来您已经完成大部分工作了。您的调查做得很好。让我们看看我是否可以帮助清除其背后的“原因”。

链接器正在执行以下操作。当您链接可执行文件(上面的“ main”)时,它具有一些未解析的符号(功能和其他内容)。它将查看随后的库列表,尝试解析未解析的符号。一路上,它发现libB.so提供了一些符号,因此它指出该库现在已解析它们。

但是,它还会发现其中一些符号使用了可执行文件中尚未解析的其他符号,因此它现在也需要解析这些符号。如果不链接libA.so,则您的应用程序将不完整。一旦针对libA.so进行链接,所有符号都将被解析并且链接完成。

如您所见,使用-unresolved-symbols-in-shared-libs并不能解决问题。它只是推迟它,以便在运行时解析这些符号。这样做的目的-rpath是:指定要在运行时搜索的库。如果这些符号无法解析,则您的应用将无法启动。

弄清楚库的依赖关系并不是一件容易的事,因为一个符号可以由多个库提供,并且可以通过与任何一个库链接来满足。

这里有对此过程的另一种描述:为什么链接库的顺序有时会导致GCC错误?


2
我知道那是很久以前的事,我忘了将其标记为已解决。非常感谢您的回答。]
Marcus

谢谢你们提出了一个令人惊奇的问题,以及一个恰当的答案!我几乎完全一样。我正在尝试将openCV共享对象包装在自定义共享对象中。可以说C.so是我的习惯,而CV.so是一些openCV。我已经成功地将C.so与CV.so链接了,并且我想将main.cpp与C.so链接(但您可以得到它),但是除了使用C.so中的对象之外,main.cpp还使用了在CV.so中定义的对象'cv :: Mat'。在这种情况下,要运行main.cpp,我需要同时链接C.so和CV.so吗?如果有人对此有所了解,我会喜欢的。谢谢!:)
Lenny Linus

2
在生成libB.so时,链接器为什么不解析所有必需的libA.so符号?主应用程序不应该链接到libA,因为它应该是透明的,例如被libB间接屏蔽?
威尔逊,

这是不正确的。链接程序除了将不完整的依赖关系链通知用户外,不使用libA.so。如果不提供libA.so,而是传递-unresolved-symbols = ignore-in-shared-libs,您将获得相同的可执行文件。
listerreg

21

对于动态链接只可以使用直接依赖-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 2015年

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
你误会了 显然,您需要解决立即需要的符号。问题与您的依赖项可能需要的符号有关。开发第三方供第三方使用的库时,库用户必须为库使用的功能提供实现,这很常见。您使用存根进行开发,根本没有任何依赖关系,集成商将开发一个lib来替换存根,然后将所有内容链接在一起。加载程序将必须解决所有依赖关系。但是,您的编译脚本(和您的用户)必须解决这一问题。
Marcus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.