我正在尝试将c代码暴露给cython并且正在运行"未定义的符号"尝试使用另一个cython模块的c文件中定义的函数时出错 使用手动包装器在我的h文件和函数中定义的函数没有问题。
基本上与this question的情况相同,但解决方案(链接到图书馆)对我来说并不令人满意。
我假设我遗漏了 extension UIImage {
func resizeImage(_ dimension: CGFloat, opaque: Bool, contentMode: UIViewContentMode = .scaleAspectFit) -> UIImage {
var width: CGFloat
var height: CGFloat
var newImage: UIImage
let size = self.size
let aspectRatio = size.width/size.height
switch contentMode {
case .scaleAspectFit:
if aspectRatio > 1 { // Landscape image
width = dimension
height = dimension / aspectRatio
} else { // Portrait image
height = dimension
width = dimension * aspectRatio
}
default:
fatalError("UIIMage.resizeToFit(): FATAL: Unimplemented ContentMode")
}
if #available(iOS 10.0, *) {
let renderFormat = UIGraphicsImageRendererFormat.default()
renderFormat.opaque = opaque
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height), format: renderFormat)
newImage = renderer.image {
(context) in
self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
}
} else {
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), opaque, 0)
self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
}
return newImage
}
}
脚本中的某些内容?
<小时/> 最小化我的案例:
foo.h中
setup.py
foo.c的
int source_func(void);
inline int header_func(void){
return 1;
}
<小时/> 的 foo_wrapper.pxd
#include "foo.h"
int source_func(void){
return 2;
}
foo_wrapper.pyx
cdef extern from "foo.h":
int source_func()
int header_func()
cdef source_func_wrapper()
<小时/> cython模块我想使用以下功能:
cdef source_func_wrapper():
return source_func()
<小时/> setup.py 同时构建
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
和foo_wrapper
test_lib
答案 0 :(得分:6)
foo_wrapper
中有3种不同类型的功能:
source_func_wrapper
是一个python函数,python运行时处理这个函数的调用。header_func
是一个在编译时使用的内联函数,因此以后不需要它的定义/机器代码。source_func
必须由静态(foo_wrapper
中的情况)或动态(我假设这是您对test_lib
)链接器的期望来处理。再向下我会尝试解释,为什么设置不能开箱即用,但我想先介绍两个(至少在我看来)最好的选择:
答:完全避免这个问题。您的foo_wrapper
来自foo.h
的c函数。这意味着每个其他模块应该使用这些包装函数。如果每个人都可以直接访问这些功能 - 这会使整个包装器过时。隐藏`pyx-file:
foo.h
接口
#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():
B:想要通过c函数直接使用foo功能可能是有效的。在这种情况下,我们应该使用与stdc++
的cython相同的策略 - 库:foo.cpp
应该成为一个共享库,并且应该只有一个foo.pdx
- 文件(没有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
有很多未定义的符号,其中大多数以Py
开头,并且在运行时期间由python可执行文件提供。但也有我们的恶人 - source_func
。
现在,我们通过
启动pythonLD_DEBUG=libs,files,symbols python
并通过import test_lib
加载我们的扩展程序。在触发的debug -trace中,我们可以看到以下内容:
>>>>: file=./test_lib.so [0]; dynamically loaded by python [0]
python通过test_lib.so
加载dlopen
并开始从test_lib.so
查找/解析未定义的符号:
>>>>: symbol=PyExc_RuntimeError; lookup in file=python [0]
>>>>: symbol=PyExc_TypeError; lookup in file=python [0]
这些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)
因此,在查找所有已加载的共享库后,找不到符号,我们必须中止。有趣的是,foo_wrapper
尚未加载,因此无法在那里查找source_func
(它将在下一步中作为test_lib
依赖于python加载。)< / p>
如果我们使用预加载的foo_wrapper.so
启动python会发生什么?
LD_DEBUG=libs,files,symbols LD_PRELOAD=$(pwd)/foo_wrapper.so python
这一次,调用import test_lib
成功,因为预加载foo_wrapper
是动态加载器查找符号的第一个位置(在python可执行文件之后):
>>>>: symbol=source_func; lookup in file=python [0]
>>>>: symbol=source_func; lookup in file=/home/ed/python_stuff/cython/two/foo_wrapper.so [0]
但是如果foo_wrapper.so
没有预加载,它是如何工作的?首先,我们将foo_wrapper.so
作为库添加到我们的test_lib
:
ext_modules = cythonize([
Extension("test_lib", ["test_lib.pyx"],
libraries=[':foo_wrapper.so'],
library_dirs=['.'],
)])
这将导致以下链接器命令:
gcc ... test_lib.o -L. -l:foo_wrapper.so -o test_lib.so
如果我们现在查找符号,那么我们认为没有区别:
>>> nm test_lib.so --undefined
....
U PyXXXXX
U source_func
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]
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.
好的,现在动态链接器知道,它必须找到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
这次找到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=['.']
)
])
现在正在调用
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
即使未通过foo_wrapper.so
设置,也会在所谓的RPATH
上找到{p> LD_LIBRARY_PATH
。我们可以看到静态链接器插入了这个RPATH
:
>>>> readelf -d test_lib.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [.]
然而,这是相对于当前工作目录的路径,这大部分时间都不是想要的。一个人应该通过绝对路径或使用
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
)
])
现在可以显示生成的shared library. readelf
所显示的相对于当前位置的路径(例如可以通过复制/移动进行更改):
>>>> readelf -d test_lib.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [$ORIGIN/.]
这意味着将相对于加载的共享库的路径搜索所需的共享库,即test_lib.so
。
如果您想重复使用我不提倡的foo_wrapper.so
符号,那么您的设置也应如此。
但是有一些可能性来使用你已经构建的库。
让我们回到原始设置。如果我们首先导入foo_wrapper
(作为一种预加载)并且只导入test_lib
会发生什么?即:
>>>> import foo_wrapper
>>>>> import test_lib
这不是开箱即用的。但为什么?显然,来自foo_wrapper
的加载符号对其他库不可见。 Python使用dlopen
来动态加载共享库,并且如this good article中所述,可能存在一些不同的策略。我们可以使用
>>>> import sys
>>>> sys.getdlopenflags()
>>>> 2
查看设置了哪些标志。 2
表示RTLD_NOW
,这意味着在加载共享库时直接解析符号。我们需要对RTLD_GLOBAL=256
进行OR标记,以使符号在动态加载的库中全局/可见。
>>> import sys; import ctypes;
>>> sys.setdlopenflags(sys.getdlopenflags()| ctypes.RTLD_GLOBAL)
>>> import foo_wrapper
>>> import test_lib
它的工作原理,我们的调试跟踪显示:
>>> symbol=source_func; lookup in file=./foo_wrapper.so [0]
>>> file=./foo_wrapper.so [0]; needed by ./test_lib.so [0] (relocation dependency)
另一个有趣的细节: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! ....
为什么这样? RTLD_LAZY
表示符号不是直接在加载时解析,而是在第一次使用时解析。但在第一次使用(test_lib.do_it()
)之前,foo_wrapper
已加载(在test_lib
模块内导入),由于RTLD_GLOBAL
,其符号可用于稍后解析。< / p>
如果我们不使用RTLD_GLOBAL
,则只有在我们致电test_lib.do_it()
时才会出现失败,因为在这种情况下,foo_wrapper
所需的符号在全局范围内无法看到。
对于这个问题,为什么仅仅将foo_wrapper
和test_lib
模块与foo.cpp
:单身人士联系起来并不是一个好主意,请参阅this。