【问题标题】:Jython import of Java package created by Swig fails to linkSwig 创建的 Java 包的 Jython 导入无法链接
【发布时间】:2015-11-25 19:23:38
【问题描述】:

我正在开发包含用多种语言编写的组件的应用程序。我正在尝试获得在 Jython 中工作的 Java 中运行良好的功能。 Java 通过 JNI 访问并由 SWIG 包装了一些本机/C++ 功能。

每当我尝试导入项目中的所有类时,都会收到无法链接 PROJECTJNI 的错误。这是我要制作的最小案例:

import sys
sys.path.append('PROJECT.jar')
from com.whatever.project import *

这是执行时的错误消息:

$ jython Bootstrap.py
"my" variable $jythonHome masks earlier declaration in same scope at /usr/bin/jython line 15.
Traceback (most recent call last):
  File "Bootstrap.py", line 9, in <module>
    from com.whatever.project import *
java.lang.UnsatisfiedLinkError: com.whatever.project.PROJECTJNI.swig_module_init()V
        at com.whatever.project.PROJECTJNI.swig_module_init(Native Method)
        at com.whatever.project.PROJECTJNI.<clinit>(PROJECTJNI.java:974)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:278)
        at org.python.core.Py.loadAndInitClass(Py.java:909)
        at org.python.core.Py.findClassInternal(Py.java:844)
        at org.python.core.Py.findClass(Py.java:869)
        at org.python.core.packagecache.PackageManager.basicDoDir(PackageManager.java:107)
        at org.python.core.packagecache.SysPackageManager.doDir(SysPackageManager.java:138)
        at org.python.core.PyJavaPackage.fillDir(PyJavaPackage.java:123)
        at org.python.core.imp.importAll(imp.java:1051)
        at org.python.core.imp.importAll(imp.java:1039)
        at org.python.pycode._pyx0.f$0(Bootstrap.py:9)
        at org.python.pycode._pyx0.call_function(Bootstrap.py)
        at org.python.core.PyTableCode.call(PyTableCode.java:165)
        at org.python.core.PyCode.call(PyCode.java:18)
        at org.python.core.Py.runCode(Py.java:1275)
        at org.python.util.PythonInterpreter.execfile(PythonInterpreter.java:235)
        at org.python.util.jython.run(jython.java:247)
        at org.python.util.jython.main(jython.java:129)

java.lang.UnsatisfiedLinkError: java.lang.UnsatisfiedLinkError: com.whatever.project.PROJECTJNI.swig_module_init()V

第 15 行的内容在我们调用 jython 时出现,我们一直忽略它。

我们可以通过简单地一次加载一个类来让 Java 项目中的类工作:

com.whatever.project import Class1
com.whatever.project import Class2
...
com.whatever.project import Class50

这是非常不切实际的,因为即使是简短的 Python 脚本,我们也可能需要十几个类。其中许多是我们正在捕获的具有独特类型的例外。因此,任何能够稳健地处理错误的东西都可能需要大量的类。

根据jython documentation,我应该能够隐藏 PROJECTJNI,因此不会通过执行此类操作来加载它,但我发现文档不太清楚。这是我尝试过的:

import com.whatever.project
__all__ = dir(com.whatever.project)
__all__.remove('PROJECTJNI')
from com.whatever.project import *

但这会因错误而失败,并且仍在尝试加载 PROJECTJNI。

我还尝试修复本机可执行文件,以便它可以与正确链接。我了解到另一个使用 JRuby 的小组没有任何问题,包括所有内容,所以我决定检查源代码和二进制文件。我在 Swig 创建的文件 Project_wrap.cpp 中找到了 void swig_module_init()。它隐藏在宏后面,但它就在那里,并且 objdump 确认:

$objdump libPROJECT.so -t |grep PROJECTJNI |grep init
000000000051a900 l     O .data  00000000000009d0              _ZZ59Java_com_whatever_project_PROJECTJNI_swig_1module_1initE7methods
0000000000263f66 g     F .text  00000000000000d1              Java_com_whatever_project_PROJECTJNI_swig_1module_1init

我的问题排查步骤是否有问题?是 Jython 中的错误吗?是否有一个简单的 Python 解决方法可以让它跳过加载 PROJECTJNI?

任何让我跳过链接或正确链接的内容都将被接受。

【问题讨论】:

    标签: java c++ import jython swig


    【解决方案1】:

    在其中一个 Java 类 BinaryLoader 上,有一个名为 void load_binary() 的方法,该方法在该类的静态部分中调用:

    public class BinaryLoader
    {
        static
            { load_binaries(); }
    
        public static void load_binaries()
        {
            // Deep inside here is a call to actually load the shared library. Using
            load_shared_library_from_jar("PROJECT");
        }
     ... // more details here
     }
    

    当类被加载时,我们使用原生 API 的 Java 代码调用了这个方法。当类被“触及”时,我们的 JRuby 代码调用它,只使用常量的名称,类的名称,调用静态部分。在这里澄清一下,我们的文档和实时 Ruby 脚本中的一个示例可以做到这一点:

    # Tells Jruby to load the Java Interopability layer.
    require 'java'
    
    # Loads the PROJECT Java tools.
    require 'PROJECT.jar'
    
    # Shorten the PROJECT tool names from their obnoxiously long Java name.
    module PROJECT
        include_package "com.whatever.project"
    end
    
    # Let Ruby know the BinaryLoader exists and it will have PROJECT load all the system binaries.
    PROJECT::BinaryLoader
    

    显然需要 JarFile 就足以使所有 Native 符号和 Java 类在 Ruby 中可用,并且直到最后一行才加载二进制文件,这只是一个解析为类名并强制静态部分的表达式跑步。实际的方法调用应该是这样的:PROJECT::BinaryLoader.load_binaries

    在 Jython 中,即使在 BinaryLoader 类上调用其他静态方法时,也不会调用静态部分。我通过导入最少的 Java 和 Native 符号来强制它手动调用该方法:

    # Tells Jython to load the python system Interopability tools.
    import sys
    
    # When searching for python symbols, this Java jar should be search also.
    sys.path.append('PROJECT.jar')
    
    # Load just enough the have the JVM Know how to load the native PROJECT libraries
    from com.whatever.project import PROJECT
    from com.whatever.project import BinaryLoader
    
    # Load any native binaries that are required
    BinaryLoader.load_binaries()
    
    # After this line is run in any given script then PROJECT can be used.
    from com.whatever.project import *
    

    显然,本机二进制文件并没有像想象的那样加载。加载这些的任何东西都是可接受的解决方案。

    这可以解决 Jython 不调用静态部分的问题,方法是尽可能早地完成它应该完成的工作。这似乎是 Jython 中的一个错误,但可能是在 Java 中没有严格执行对静态加载顺序的保证。无论哪种方式,因为这种不兼容,我们可能会删除静态部分,以防止未来的 Java 广告 Ruby Devs 添加可能无法在 Python 中加载的内容。

    【讨论】:

      猜你喜欢
      • 2017-10-13
      • 2013-01-30
      • 1970-01-01
      • 1970-01-01
      • 2014-12-03
      • 2021-04-19
      • 2016-11-06
      • 2016-11-12
      • 1970-01-01
      相关资源
      最近更新 更多