关于c ++:使用SWIG生成Java接口

Generating Java interface with SWIG

我使用SWIG来制作一个C++库的Java包装器(关于JSON(DE)序列化),以便在Android上使用它。我在C++中定义了一个抽象类,表示一个可以(DE)序列化的对象:

1
2
3
4
5
class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0;
    virtual void deserialize(Value &root) = 0;
};

现在,我试着从这个类中生成一个Java接口。这是我的swig接口:

1
2
3
4
5
6
7
8
9
10
11
12
%module JsonSerializable
%{
#include"JsonSerializable.hpp"
%}

%import"JsonValue.i"

class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0;
    virtual void deserialize(Value &root) = 0;
};

但是生成的Java代码是(很明显,因为我无法找到如何告诉SWIG这是一个接口)一个简单的类,用这两种方法和一个默认的构造函数/析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class IJsonSerializable {
  private long swigCPtr;
  protected boolean swigCMemOwn;

  public IJsonSerializable(long cPtr, boolean cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
  }  

  public static long getCPtr(IJsonSerializable obj) {
    return (obj == null) ? 0 : obj.swigCPtr;
  }  

  protected void finalize() {
    delete();
  }  

  public synchronized void delete() {
    if (swigCPtr != 0) {
      if (swigCMemOwn) {
        swigCMemOwn = false;
        JsonSerializableJNI.delete_IJsonSerializable(swigCPtr);
      }  
      swigCPtr = 0;
    }  
  }  

  public void serialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_serialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

  public void deserialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_deserialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

}

如何使用swig生成有效的接口?


你可以用Sigg+Java使用"导演"来实现你想要的东西,但是它并不像你希望的那样直接从C++抽象类映射到Java。因此,我的答案分为三个部分:首先,在Java中实现C++纯虚拟函数的简单示例;其次,解释为什么输出是这样的,第三个是"工作"。

用Java实现C++接口

给定头文件(module.hh):

1
2
3
4
5
6
7
8
9
10
11
12
#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;
}

我们想把它包起来,让它从Java端直观地工作。我们可以通过定义以下swig接口来实现这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
%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.
"
+ e);
      System.exit(1);
    }
  }
%}

在这里,我们为整个模块启用了控制器,然后要求它们专门用于class Interface。除了这个和我最喜欢的"自动加载共享对象"代码之外,没有什么特别值得注意的。我们可以用下面的Java类来测试:

1
2
3
4
5
6
7
8
9
public class Run extends Interface {
  public static void main(String[] argv) {
    test.bar(new Run());      
  }

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

然后我们可以运行它并看到它按预期工作:

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

如果你对既不是abstract也不是interface感到满意,你可以停止阅读,导演会做你需要的一切。

为什么swig生成一个class而不是interface

然而,Swig已经将看起来像抽象类的类变成了具体类。这意味着在Java方面,我们可以合法地编写EDCOX1×6,这是没有意义的。为什么Swig会这么做?EDOCX1的4Ω甚至不是EDCOX1×2,更不用说EDCOX1×3 3(参见这里的4点),这在Java方面会更自然。答案是双重的:

  • SWIG提供了调用EDCOX1×10的机制,在Java端操作EDCOX1和11等。这在一台interface上根本做不到。
  • 考虑我们包装以下函数的情况:

    1
    Interface *find_interface();

    在这里,Swig对返回类型的了解不比它的类型interface多。在理想的情况下,它会知道派生类型是什么,但是仅仅从函数签名中,它就没有办法解决这个问题。这意味着,在生成的Java某处,必须对EDCOX1的14个调用进行调用,如果EDCOX1(3)在Java端是抽象的,则这是不可能的/合法的。

  • 可能的解决方法

    如果您希望将此作为接口来表示Java中的多重继承的类型层次结构,这将是相当有限的。不过,还有一个解决办法:

  • 手动编写接口作为一个合适的Java接口:

    1
    2
    3
    public interface Interface {
        public String foo();
    }
  • 修改swig接口文件:

  • 在Java端重命名C++类EDCOX1,3,以EDOCX1,17为单位。(我们也应该让它只对有问题的包可见,我们的包装代码活在它自己的包中,以避免人们做"疯狂"的事情。
  • 在C++代码SWIG中,我们到处都有EDOCX1 3,现在将使用EDCOX1×17作为Java端的类型。我们需要类型映射来将EDCOX1的17个函数映射到手动添加的EDCOX1、3、Java接口上。
  • 标记EDCOX1(17)作为实现EDCOX1(3)的方式,使Java端行为对Java用户来说是自然可信的。
  • 我们需要提供一点额外的代码,这些代码可以作为实现Java EDCOX1第3条的东西的代理,而不是EDCOX1的17位。
  • 我们传递给C++的总是必须是一个EDCOX1,17个,但不是所有的EDCOX1,3个s都是一个(尽管所有的EDCOX1都是28),所以我们提供一些粘接剂,使EDCOX1,3的行为像EDCOX1,28,和一个类型映射来应用这个胶水。(有关pgcppname的讨论,见本文件)
  • 这会产生一个模块文件,现在看起来像:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    %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);
      }
    %}

    现在我们可以包装一个函数,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // %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;
      }
    %}

    使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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!";
      }
    }

    这就像你希望的那样运行:

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

    正如Java程序员所期望的那样,我们在C++中封装了一个抽象类作为Java的接口!