Python CFFI动态链接依赖库失败:include方法为何无效?
问题场景
尝试用Python CFFI实现依赖C库的动态链接,其中库foo_b依赖foo_a的bar函数并对外暴露baz函数,但导入ffi_foo_b时崩溃,提示未定义符号"bar",尽管已经调用ffi_b.include(ffi_a)。
示例代码
from cffi import FFI from pathlib import Path Path('foo_a.h').write_text(""" int bar(int x); """) Path('foo_a.c').write_text(""" #include "foo_a.h" int bar(int x) { return x + 69; } """) Path('foo_b.h').write_text(""" int baz(int x); """) Path('foo_b.c').write_text(""" #include "foo_a.h" #include "foo_b.h" int baz(int x) { return bar(x * 100); } """) ffi_a = FFI() ffi_b = FFI() ffi_a.cdef('int bar(int x);') ffi_a.set_source('ffi_foo_a', '#include "foo_a.h"', sources=['foo_a.c']) ffi_a.compile() ffi_b.cdef('int baz(int x);') ffi_b.include(ffi_a) ffi_b.set_source('ffi_foo_b', '#include "foo_b.h"', sources=['foo_b.c']) ffi_b.compile() import ffi_foo_a if ffi_foo_a.lib.bar(1) == 70: print('foo_a OK') else: raise AssertionError('foo_a ERR') import ffi_foo_b # 此处崩溃:未定义符号"bar" if ffi_foo_b.lib.baz(420) == 42069: print('foo_b OK') else: raise AssertionError('foo_b ERR')
问题疑惑
根据CFFI文档说明:
For out-of-line modules, the
ffibuilder.include(other_ffibuilder)line should occur in the build script, and theother_ffibuilderargument should be another FFI instance that comes from another build script. When the two build scripts are turned into generated files, say_ffi.soand_other_ffi.so, then importing_ffi.sowill internally cause_other_ffi.soto be imported. At that point, the real declarations from_other_ffi.soare combined with the real declarations from_ffi.so.
为何include()没有解决链接问题?正确的跨模块依赖链接方式是什么?
问题原因
你误解了ffibuilder.include()的作用:它仅用于共享C类型/函数的声明,让CFFI在生成Python绑定代码时能识别跨模块的类型签名,不会自动处理底层C库的链接依赖。你的foo_b.c编译过程中,编译器没有被指示去链接包含bar函数实现的库,导致生成的ffi_foo_b.so中bar是未定义符号,运行时无法找到。
正确解决方法
方案1:编译ffi_foo_b时显式链接ffi_foo_a的扩展库
修改ffi_b的set_source调用,添加链接参数,告诉编译器要链接ffi_foo_a生成的共享库:
ffi_b.set_source( 'ffi_foo_b', '#include "foo_b.h"', sources=['foo_b.c'], libraries=['ffi_foo_a'], # 指定要链接的库名(无需前缀/后缀) library_dirs=['.'], # 库所在的目录(当前工作目录) runtime_library_dirs=['.'] # Unix系统需要:指定运行时库搜索路径 )
- Windows系统下,库文件为
ffi_foo_a.lib,无需设置runtime_library_dirs; - Unix系统下,生成的库文件是
libffi_foo_a.so,libraries参数只需写ffi_foo_a即可。
方案2:将foo_a编译为独立静态库,让foo_b直接链接
如果不想依赖Python扩展库的链接关系,可以先把foo_a编译成静态库,再让foo_b链接它:
- 编译foo_a为静态库(以gcc为例):
gcc -c foo_a.c -o foo_a.o ar rcs libfoo_a.a foo_a.o
- 修改ffi_b的
set_source:
ffi_b.set_source( 'ffi_foo_b', '#include "foo_b.h"', sources=['foo_b.c'], libraries=['foo_a'], library_dirs=['.'] )
这种方式下,foo_b会直接把静态库中的bar实现编译进自身的扩展库,无需依赖ffi_foo_a模块。
注意事项
ffi_b.include(ffi_a)仍然需要保留,它确保CFFI能正确识别baz函数调用bar时的类型签名,避免出现类型不匹配的错误。
内容的提问来源于stack exchange,提问作者JamesTheAwesomeDude

