且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用SWIG生成Java界面

更新时间:2023-11-27 10:12:16

您可以使用导向器,但是它不是从C ++抽象类到Java可能希望的直接映射。因此,我的答案分为三部分 - 首先是在Java中实现C ++纯虚函数的简单例子,其次解释为什么输出是这样的,第三是一个工作。

You can achieve what you're looking for with SWIG+Java using "Directors", however it's not quite as straightforward mapping from the C++ abstract classes onto Java as you might hope. My answer therefore is split into three parts - firstly the simple example of implementing a C++ pure virtual function in Java, secondly an explanation of why the output is like that and thirdly a "work-around".

给定一个头文件( module.hh ) :

Given a header file (module.hh):

#include <string>
#include <iosfwd>

class Interface {
public:
  virtual std::string foo() const = 0;
  virtual ~Interface() {}
};

inline void bar(const Interface& intf) {
  std::cout << intf.foo() << std::endl;
}

我们想要将它包装起来, 。我们可以通过定义以下SWIG界面来实现这一点:

We'd like to wrap this and make it work intuitively from the Java side. We can do this by defining the following SWIG interface:

%module(directors="1") test

%{
#include <iostream>
#include "module.hh"
%}

%feature("director") Interface;
%include "std_string.i"

%include "module.hh"

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("module");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

导致整个模块,然后请求它们用于 class Interface 具体。除此之外,我最喜欢的加载共享对象自动代码没有什么特别值得注意。我们可以用下面的Java类来测试:

Here we've enabled directors for the whole module, and then requested they be used for class Interface specifically. Other than that and my favourite "load the shared object automatically" code there's nothing particularly noteworthy. We can test this with the following Java class:

public class Run extends Interface {
  public static void main(String[] argv) {
    test.bar(new Run());       
  }

  public String foo() {
    return "Hello from Java!";
  }
}

然后我们可以运行它, :

We can then run this and see it's working as expected:


ajw @ rapunzel:〜/ code / scratch / swig / javaintf> java运行

Hello!

ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!

如果您对此感到满意,既不是抽象

If you're happy with it being neither abstract nor an interface you can stop reading here, directors do everything you need.

类成一个具体的。这意味着在Java端我们可以合法地写 new Interface(); ,这没有意义。为什么SWIG这样做? class 甚至不是 abstract ,更不用说接口(请参阅第4点此处),这在Java端感觉更自然。答案是双重的:

SWIG has however made what looked like an abstract class into a concrete one. That means on the Java side we could legally write new Interface();, which makes no sense. Why does SWIG do this? The class isn't even abstract, let alone an interface (See point 4 here), which would feel more natural on the Java side. The answer is twofold:


  1. SWIG提供调用 delete 的机制, code> cPtr 等。这在接口中完全不能实现。

  2. 考虑下面的情况: / p>

  1. SWIG supplies mechanics for calling delete, manipulating the cPtr etc. on the Java side. That couldn't be done in an interface at all.
  2. Consider the case where we wrapped the following function:

Interface *find_interface();

在这里SWIG知道没有什么更多的返回类型比它的类型接口。在理想的世界里,它会知道派生类型是什么,但是从函数签名本身就没有办法解决这个问题。这意味着在生成的Java 某处,必须调用新接口,这将是不可能的/ legal if 接口在Java端是抽象的。

Here SWIG knows nothing more about the return type than that it's of type Interface. In an ideal world it would know what the derived type is, but from the function signature alone there's no way for it to figure this out. This means that in the generated Java somewhere there's going to have to be a call to new Interface, which wouldn't be possible/legal if Interface were abstract on the Java side.



/ h1>

如果你希望提供这个接口为了在Java中表达一个具有多重继承的类型层次结构,这将是非常有限的。但是有一个解决方法:

Possible workaround

If you were hoping to provide this as an interface in order to express a type hierarchy with multiple inheritance in Java this would be quite limiting. There's a workaround however:


  1. 将接口手动编写为一个合适的Java接口:

  1. Manually write the interface as a proper Java interface:

public interface Interface {
    public String foo();
}


  • 修改SWIG界面文件:

  • Modify the SWIG interface file:


    1. 重命名C ++类接口 NativeInterface Java端。 (我们应该让它只能看到有关的包,我们的包装的代码生活在自己的包,以避免人们做疯狂的东西。

    2. 到处我们有一个在C ++代码中的接口 SWIG现在将使用 NativeInterface 作为Java端的类型,我们需要类型映射 NativeInterface 在我们手动添加的接口 Java接口中。

    3. 标记 NativeInterface 作为实现接口,使Java端行为对Java用户是自然和可信的。

    4. 我们需要提供一些额外的代码,它们可以作为实现Java 接口的代理,而不是 NativeInterface

    5. 传递给C ++的内容必须始终是 NativeInterface code>接口将是一个虽然(虽然所有 NativeInterfaces 将),所以我们提供一些胶水使 Interface 的行为如 NativeInterfaces ,以及一个应用该粘合的typemap。 (有关 pgcppname $ c的讨论,请参见此文档 $ c>)

    1. Rename the C++ class Interface to be NativeInterface on the Java side. (We ought to make it visible only to the package in question too, with our wrapped code living in a package of its own to avoid people doing "crazy" things.
    2. Everywhere we have an Interface in C++ code SWIG will now be using NativeInterface as the type on the Java side. We need typemaps to map this NativeInterface in function parameters onto the Interface Java interface we added manually.
    3. Mark NativeInterface as implementing Interface to make the Java side behaviour natural and believable to a Java user.
    4. We need to supply a little bit of extra code that can act as a proxy for things which implement the Java Interface without being a NativeInterface too.
    5. What we pass to C++ must always be a NativeInterface still, not all Interfaces will be one though (although all NativeInterfaces will), so we provide some glue to make Interfaces behave as NativeInterfaces, and a typemap to apply that glue. (See this document for a discussion of the pgcppname)

    这将产生一个模块文件,如下所示:

    This results in a module file that now looks like:

    %module(directors="1") test
    
    %{
    #include <iostream>
    #include "module.hh"
    %}
    
    %feature("director") Interface;
    %include "std_string.i"
    
    // (2.1)
    %rename(NativeInterface) Interface; 
    
    // (2.2)
    %typemap(jstype) const Interface& "Interface";
    
    // (2.3)
    %typemap(javainterfaces) Interface "Interface"
    
    // (2.5)
    %typemap(javain,pgcppname="n",
             pre="    NativeInterface n = makeNative($javainput);")
            const Interface&  "NativeInterface.getCPtr(n)"
    
    %include "module.hh"
    
    %pragma(java) modulecode=%{
      // (2.4)
      private static class NativeInterfaceProxy extends NativeInterface {
        private Interface delegate;
        public NativeInterfaceProxy(Interface i) {
          delegate = i;
        }
    
        public String foo() {
          return delegate.foo();
        }
      }
    
      // (2.5)
      private static NativeInterface makeNative(Interface i) {
        if (i instanceof NativeInterface) {
          // If it already *is* a NativeInterface don't bother wrapping it again
          return (NativeInterface)i;
        }
        return new NativeInterfaceProxy(i);
      }
    %}
    


  • 现在我们可以包装一个函数:

    Now we can wrap a function like:

    // %inline = wrap and define at the same time
    %inline %{
      const Interface& find_interface(const std::string& key) {
        static class TestImpl : public Interface {
          virtual std::string foo() const {
            return "Hello from C++";
          }
        } inst;
        return inst;
      }
    %}
    

    并使用它:

    import java.util.ArrayList;
    
    public class Run implements Interface {
      public static void main(String[] argv) {
        ArrayList<Interface> things = new ArrayList<Interface>();
        // Implements the interface directly
        things.add(new Run()); 
        // NativeInterface implements interface also
        things.add(test.find_interface("My lookup key")); 
    
        // Will get wrapped in the proxy 
        test.bar(things.get(0));
    
        // Won't get wrapped because of the instanceOf test
        test.bar(things.get(1));
      }
    
      public String foo() {
        return "Hello from Java!";
      }
    }
    

    p>

    This now runs as you'd hope:


    ajw @ rapunzel:〜/ code / scratch / swig / javaintf> javaRun└
    Hello from Java! />
    C ++中的Hello

    ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
    Hello from Java!
    Hello from C++

    我们已经将来自C ++的抽象类作为Java中的接口Java程序员会期望!

    And we've wrapped an abstract class from C++ as an interface in Java exactly as a Java programmer would expect!