加载动态库的顺序重要吗?

Does the order in which dynamic libraries are loaded matter?

我在 OS X 上并使用 Homebrew 安装了 Gtk+3 包。

brew install gtk+3

我可以使用 ctypes 模块加载 Python 中安装的库。

$ python2.6
Python 2.6.9 (unknown, Oct 23 2015, 19:19:20)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import cdll
>>> cdll.LoadLibrary('/usr/local/lib/libatk-1.0.0.dylib')
<CDLL '/usr/local/lib/libatk-1.0.0.dylib', handle 7fbd10f1a250 at 10aa33210>
>>> cdll.LoadLibrary('/usr/local/lib/libglib-2.0.0.dylib')
<CDLL '/usr/local/lib/libglib-2.0.0.dylib', handle 7fbd10f0ffb0 at 10aa22dd0>
>>> ^D

到目前为止一切顺利。困扰我的一件事是,如果我尝试加载 与上面相同的两个库,但顺序不同,它会抛出 未找到符号 异常。

$ python2.6
Python 2.6.9 (unknown, Oct 23 2015, 19:19:20)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import cdll
>>> cdll.LoadLibrary('/usr/local/lib/libglib-2.0.0.dylib')
<CDLL '/usr/local/lib/libglib-2.0.0.dylib', handle 7fad13d00d60 at 10a688210>
>>> cdll.LoadLibrary('/usr/local/lib/libatk-1.0.0.dylib')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/ctypes/__init__.py", line 423, in LoadLibrary
    return self._dlltype(name)
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/ctypes/__init__.py", line 345, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: dlopen(/usr/local/lib/libatk-1.0.0.dylib, 6): Symbol not found: _g_free
  Referenced from: /usr/local/lib/libatk-1.0.0.dylib
  Expected in: flat namespace
 in /usr/local/lib/libatk-1.0.0.dylib
>>> ^D

因此,先加载 atk,然后加载 glib,就可以了。反之则不然。谁能解释这种行为?

这是使用 C 在命令行级别显示的相同问题。在这里,我制作了一个 main.c,它在 foo.so 中调用 foo(),在 [=] 中调用 bar() 27=],它在 glib 中调用(为了好玩)g_free 和 g_malloc。

$ ls -Flas main.o libfoo.so libbar.so | cut -c3-13,34-37,51-
-rwxrwxr-x 2976 libbar.so*    # has bar(), which calls g_free/g_malloc
-rwxrwxr-x 3504 libfoo.so*    # has foo(), which calls bar()   
-rw-rw-r-- 2864 main.o        # has main(), which calls foo()

正确的链接命令:

$ gcc -o main main.o -L. -lfoo -lbar -lglib-2.0
$ LD_LIBRARY_PATH=. ./main
worked!

正在尝试翻转库加载顺序:

$ gcc -o main main.o -L. -lbar -lfoo -lglib-2.0
./libfoo.so: undefined reference to `bar'
collect2: error: ld returned 1 exit status

这是因为您遇到的问题本质上是相同的。 "man ld" 的相关部分是这样的:

If the archive defines a symbol which was undefined in some object which appeared before the archive on the command line, the linker will include the appropriate file(s) from the archive. However, an undefined symbol in an object appearing later on the command line will not cause the linker to search the archive again.

因此,即使通过 cdll 加载动态库时,似乎也需要牢记这一点。