JNI调用Cython生成库‘undefined symbol: PyInit_’问题


最近项目需要提升所有 Python 算法的执行时间,并给 Java 框架调用,根据 Python一键转Jar包,Java调用Python新姿势!的思路可以用 Cython 将 Python 代码转换为 C 代码再编译为动态连接库 (so / dll),提升 Python 代码执行速度。同时提供 Java Native 接口以供 Java 框架调用。

问题

但在根据刚刚所提博文进行复现时,出现了一个老大难问题,一直都没什么头绪

Exception in thread "main" java.lang.UnsatisfiedLinkError: /path/libTest.cpython-36m-x86_64-linux-gnu.so: /path/libTest.cpython-36m-x86_64-linux-gnu.so: undefined symbol: PyInit_Test
	at java.lang.ClassLoader$NativeLibrary.load(Native Method)
	at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
	at java.lang.Runtime.load0(Runtime.java:809)
	at java.lang.System.load(System.java:1086)
	at Test.(Test.java:6)
	at Demo.main(Demo.java:3)

直到看到这篇博客 解決 Linux 上 C/C++ 的 undefined symbol 或 undefined reference,可以通过 nm -D LIBRARY_FILE 来查看动态代码段的相关符号 (symbol),输出信息如下

0000000000004a10 T Java_Test_uninitModule
0000000000004a20 T Java_Test_upperFunction
                 ......
                 U PyImport_ImportModule
                 U PyInit_Test
00000000000042e7 T PyInit_libTest
                 ......

其中,T 表示全局/局部符号,U 表示为未定义的符号。

原因

也就是说,这个 PyInit_Test 函数未定义,但 PyInit_libTest 却定义了,明明在 main.c 中没有定义这个lib函数,唯一出现的地方就是在编译文件 setup.py

extensions = [Extension("libTest", sourcefiles,
  include_dirs=['/usr/lib/jvm/java-8-oracle/include/',
    '/usr/lib/jvm/java-8-oracle/include/linux/',
    '/python/path/include/python3.6m/'],
  library_dirs=['/python/path/lib/'],
  libraries=['python3.6m'])]

第一个参数是 name,表示的是这个 extension 的全称,而之后的调用就会依托这个名字,同时在 main.cPyInit_ 之后接的是该模块的名称。也就是说,当这两个名称相同的时候,Cython 才能成功编译这个模块。

解决方案

因此将参数 name"libTest" 替换为 "Test",问题解决。

重新用 nm -D Test.so 来查看,可以看到 PyInit_Test这个函数已定义,收工~

00000000000042b7 T PyInit_Test