我试图将C代码公开给cython,并尝试使用另一个cython模块中的c文件中定义的函数时遇到“未定义符号”错误。
我的h文件中定义的函数以及使用手动包装的函数都可以正常工作。
基本上与这个问题相同,但是解决方案(针对库的链接)对我来说并不令人满意。
我认为我在setup.py脚本中缺少什么?
foo.h
int source_func(void);
inline int header_func(void){
return 1;
}
Run Code Online (Sandbox Code Playgroud)
foo.c
#include "foo.h"
int source_func(void){
return 2;
}
Run Code Online (Sandbox Code Playgroud)
cdef extern from "foo.h":
int source_func()
int header_func()
cdef source_func_wrapper()
Run Code Online (Sandbox Code Playgroud)
foo_wrapper.pyx
cdef source_func_wrapper():
return source_func()
Run Code Online (Sandbox Code Playgroud)
cimport foo_wrapper
def do_it():
print "header func"
print foo_wrapper.header_func() # ok
print "source func wrapped"
print foo_wrapper.source_func_wrapper() # ok
print "source func"
print foo_wrapper.source_func() # undefined symbol: source_func
Run Code Online (Sandbox Code Playgroud)
foo_wrapper和test_lib
cdef extern from "foo.h":
int source_func()
int header_func()
cdef source_func_wrapper()
Run Code Online (Sandbox Code Playgroud)
共有3种不同类型的功能foo_wrapper:
source_func_wrapper 是python函数,并且python运行时处理此函数的调用。header_func 是在编译时使用的内联函数,因此以后不需要其定义/机器代码。source_func另一方面,必须由静态(这种情况在foo_wrapper)或动态(我假设这是您的期望test_lib)链接器处理。接下来,我将尝试解释为什么设置不能立即使用,但是我首先要介绍两种(至少在我看来)最佳选择:
答: 完全避免此问题。您foo_wrapper从包装W函数foo.h。这意味着每个其他模块都应使用这些包装函数。如果每个人都可以直接访问功能-这将使整个包装器过时。将foo.h界面隐藏在您的`pyx文件中:
#foo_wrapper.pdx
cdef source_func_wrapper()
cdef header_func_wrapper()
#foo_wrapper.pyx
cdef extern from "foo.h":
int source_func()
int header_func()
cdef source_func_wrapper():
return source_func()
cdef header_func_wrapper():
Run Code Online (Sandbox Code Playgroud)
B:直接通过c函数使用foo功能可能是有效的。在这种情况下,我们应该使用与cython相同的策略以及stdc++-library:foo.cpp应该成为共享库,并且应该只有foo.pdx-file(没有pyx!)可以通过cimport任何需要的位置导入。此外,libfoo.so然后应将其添加为对foo_wrapper和的依赖test_lib。
但是,方法B意味着更加忙碌-您需要将libfoo.so动态加载程序放置在可以找到它的地方...
其他选择:
正如我们将看到的,有很多方法可以使foo_wrapper+ test_lib开始工作。首先,让我们更详细地了解如何在python中加载动态库。
我们首先看一下test_lib.so手边:
>>> nm test_lib.so --undefined
....
U PyXXXXX
U source_func
Run Code Online (Sandbox Code Playgroud)
有很多未定义的符号,大多数Py以python可执行文件开头,并且会在运行时提供。但也有我们的邪恶者- source_func。
现在,我们通过以下方式启动python
LD_DEBUG=libs,files,symbols python
Run Code Online (Sandbox Code Playgroud)
并通过加载我们的扩展程序import test_lib。在触发的debug -trace中,我们可以看到以下内容:
>>>>: file=./test_lib.so [0]; dynamically loaded by python [0]
Run Code Online (Sandbox Code Playgroud)
python test_lib.so通过加载dlopen并开始查找/解析来自的未定义符号test_lib.so:
>>>>: symbol=PyExc_RuntimeError; lookup in file=python [0]
>>>>: symbol=PyExc_TypeError; lookup in file=python [0]
Run Code Online (Sandbox Code Playgroud)
这些python符号很快就能找到-它们都在python-executable中定义-动态链接程序首先查看(如果此可执行文件与链接-Wl,-export-dynamic)。但这与source_func以下内容不同:
>>>>: symbol=source_func; lookup in file=python [0]
>>>>: symbol=source_func; lookup in file=/lib/x86_64-linux-gnu/libpthread.so.0 [0]
...
>>>>: symbol=source_func; lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
>>>>: ./test_lib.so: error: symbol lookup error: undefined symbol: source_func (fatal)
Run Code Online (Sandbox Code Playgroud)
因此,在查找所有已加载的共享库后,找不到该符号,因此我们必须中止。有趣的是,foo_wrapper尚未加载,因此source_func无法在此处查找(下一步将作为test_libpython的依赖项加载)。
如果我们以预加载的方式启动python会发生什么foo_wrapper.so?
LD_DEBUG=libs,files,symbols LD_PRELOAD=$(pwd)/foo_wrapper.so python
Run Code Online (Sandbox Code Playgroud)
这次,调用import test_lib成功,因为预加载foo_wrapper是动态加载器首先查找符号的地方(在python-executable之后):
>>>>: symbol=source_func; lookup in file=python [0]
>>>>: symbol=source_func; lookup in file=/home/ed/python_stuff/cython/two/foo_wrapper.so [0]
Run Code Online (Sandbox Code Playgroud)
但是,如果foo_wrapper.so未预加载,它如何工作?首先,让foo_wrapper.so我们作为库添加到我们的设置中test_lib:
ext_modules = cythonize([
Extension("test_lib", ["test_lib.pyx"],
libraries=[':foo_wrapper.so'],
library_dirs=['.'],
)])
Run Code Online (Sandbox Code Playgroud)
这将导致以下链接器命令:
gcc ... test_lib.o -L. -l:foo_wrapper.so -o test_lib.so
Run Code Online (Sandbox Code Playgroud)
如果我们现在查找符号,那么我们看不到任何区别:
>>> nm test_lib.so --undefined
....
U PyXXXXX
U source_func
Run Code Online (Sandbox Code Playgroud)
source_func仍未定义!那么,链接共享库的好处是什么?区别在于,现在foo_wrapper.so按需要列出了test_lib.so:
>>>> readelf -d test_lib.so| grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [foo_wrapper.so]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
Run Code Online (Sandbox Code Playgroud)
ld不链接,这是动态链接程序的工作,但是它可以空运行并通过注释来帮助动态链接程序,这foo_wrapper.so是解析符号所必需的,因此必须在搜索符号开始之前将其加载。但是,它并未明确指出source_func必须查看该符号foo_wrapper.so-我们实际上可以找到它并在任何地方使用它。
让我们再次启动python,这次不进行预加载:
>>>> LD_DEBUG=libs,files,symbols python
>>>> import test_lib
....
>>>> file=./test_lib.so [0]; dynamically loaded by python [0]....
>>>> file=foo_wrapper.so [0]; needed by ./test_lib.so [0]
>>>> find library=foo_wrapper.so [0]; searching
>>>> search cache=/etc/ld.so.cache
.....
>>>> `foo_wrapper.so: cannot open shared object file: No such file or directory.
Run Code Online (Sandbox Code Playgroud)
好的,现在动态链接器知道了,它必须找到foo_wrapper.so但它在路径中无处,因此我们收到一条错误消息。
我们必须告诉动态链接器在哪里寻找共享库。有很多方法,其中一种是设置LD_LIBRARY_PATH:
LD_DEBUG=libs,symbols,files LD_LIBRARY_PATH=. python
>>>> import test_lib
....
>>>> find library=foo_wrapper.so [0]; searching
>>>> search path=./tls/x86_64:./tls:./x86_64:. (LD_LIBRARY_PATH)
>>>> ...
>>>> trying file=./foo_wrapper.so
>>>> file=foo_wrapper.so [0]; generating link map
Run Code Online (Sandbox Code Playgroud)
foo_wrapper.so找到了这个时间(动态加载程序查看了由提示的位置LD_LIBRARY_PATH),然后加载并用于解析中的未定义符号test_lib.so。
但是,如果使用runtime_library_dirs-setup参数,有什么区别?
ext_modules = cythonize([
Extension("test_lib", ["test_lib.pyx"],
libraries=[':foo_wrapper.so'],
library_dirs=['.'],
runtime_library_dirs=['.']
)
])
Run Code Online (Sandbox Code Playgroud)
现在打电话
LD_DEBUG=libs,symbols,files python
>>>> import test_lib
....
>>>> file=foo_wrapper.so [0]; needed by ./test_lib.so [0]
>>>> find library=foo_wrapper.so [0]; searching
>>>> search path=./tls/x86_64:./tls:./x86_64:. (RPATH from file ./test_lib.so)
>>>> trying file=./foo_wrapper.so
>>>> file=foo_wrapper.so [0]; generating link map
Run Code Online (Sandbox Code Playgroud)
foo_wrapper.soRPATH即使未通过设置,也可以在所谓的上找到LD_LIBRARY_PATH。我们可以看到它RPATH是由静态链接器插入的:
>>>> readelf -d test_lib.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [.]
Run Code Online (Sandbox Code Playgroud)
但是,这是相对于当前工作目录的路径,在大多数情况下,这不是所需的路径。一个人应该走绝对的道路或使用
ext_modules = cythonize([
Extension("test_lib", ["test_lib.pyx"],
libraries=[':foo_wrapper.so'],
library_dirs=['.'],
extra_link_args=["-Wl,-rpath=$ORIGIN/."] #rather than runtime_library_dirs
)
])
Run Code Online (Sandbox Code Playgroud)
使相对于当前位置的路径(可以通过复制/移动来更改)shared library. readelf现在显示的结果:
>>>> readelf -d test_lib.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [$ORIGIN/.]
Run Code Online (Sandbox Code Playgroud)
这意味着将相对于已加载的共享库的路径(即)搜索所需的共享库test_lib.so。
如果您想重用foo_wrapper.so我不主张使用的符号,那也应该是您的设置。
但是,可以使用一些已经建立的库。
让我们回到原始设置。如果我们先导入foo_wrapper(作为一种预加载)然后才导入,该test_lib怎么办?即:
>>>> import foo_wrapper
>>>>> import test_lib
Run Code Online (Sandbox Code Playgroud)
开箱即用,这是行不通的。但为什么?显然,从中加载的符号foo_wrapper对其他库不可见。Python dlopen用于动态加载共享库,并且如本文所述,有几种可能的策略。我们可以用
>>>> import sys
>>>> sys.getdlopenflags()
>>>> 2
Run Code Online (Sandbox Code Playgroud)
查看设置了哪些标志。2means RTLD_NOW,这意味着在加载共享库时直接解析符号。我们需要使用OR标志,RTLD_GLOBAL=256以使符号在动态加载的库的全局/外部可见。
>>> import sys; import ctypes;
>>> sys.setdlopenflags(sys.getdlopenflags()| ctypes.RTLD_GLOBAL)
>>> import foo_wrapper
>>> import test_lib
Run Code Online (Sandbox Code Playgroud)
并且它可以正常工作,我们的调试跟踪显示:
>>> symbol=source_func; lookup in file=./foo_wrapper.so [0]
>>> file=./foo_wrapper.so [0]; needed by ./test_lib.so [0] (relocation dependency)
Run Code Online (Sandbox Code Playgroud)
另一个有趣的细节:foo_wrapper.so一次加载,因为python不会通过import两次加载一个模块foo_wrapper。但是,即使将其打开两次,在内存中也只能打开一次(第二次只读只会增加共享库的引用计数)。
但是现在有了赢得的见识,我们甚至可以走得更远:
>>>> import sys;
>>>> sys.setdlopenflags(1|256)#RTLD_LAZY+RTLD_GLOBAL
>>>> import test_lib
>>>> test_lib.do_it()
>>>> ... it works! ....
Run Code Online (Sandbox Code Playgroud)
为什么这个?RTLD_LAZY表示符号不是直接在加载时解析,而是在首次使用时解析。但是在第一次使用(test_lib.do_it())之前,先foo_wrapper加载(导入test_lib模块内部),并且由于RTLD_GLOBAL其符号可用于以后的解析。
如果不使用RTLD_GLOBAL,则仅在调用时才会失败test_lib.do_it(),因为foo_wrapper在这种情况下不会全局看到所需的符号。
为了这个问题,为什么它不是一个好主意只是为了两个模块链接foo_wrapper和test_lib反对foo.cpp:单身,看到这个。
| 归档时间: |
|
| 查看次数: |
920 次 |
| 最近记录: |