使用SWIG生成Java接口

时间:2011-11-17 14:13:04

标签: java c++ java-native-interface swig

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

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

现在,我正在尝试从这个类生成一个Java接口。这是我的SWIG界面:

%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这是一个接口)一个简单的类,有两个方法和一个默认的构造函数/析构函数:

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生成有效的界面?

1 个答案:

答案 0 :(得分:50)

使用“Directors”可以使用SWIG + Java实现您的目标,但是您可能希望将C ++抽象类映射到Java上并不是那么简单。因此,我的答案分为三个部分 - 首先是在Java中实现C ++纯虚函数的简单示例,其次是对输出为何如此,第三是“解决方法”的解释。

用Java实现C ++接口

给定头文件(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;
}

我们希望将它包装起来并使其从Java端直观地工作。我们可以通过定义以下SWIG接口来完成此任务:

%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类来测试它:

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&gt; java运行
  来自Java的你好!

如果您对abstractinterface既不会在此处停止阅读感到满意,那么导演会为您提供所需的一切。

为什么SWIG会生成class而不是interface

然而,SWIG将看起来像抽象类的东西变成了具体的类。这意味着在Java方面,我们可以合法地编写new Interface();,这没有任何意义。为什么SWIG会这样做? class甚至不是abstract,更不用说interface(参见第4点here),这在Java方面会更自然。答案是双重的:

  1. SWIG提供用于调用delete的机制,在Java端操纵cPtr等。这根本不能在interface中完成。
  2. 考虑我们包装以下函数的情况:

    Interface *find_interface();
    

    这里SWIG知道 nothing 更多关于返回类型的信息,而不是类型Interface。在一个理想的世界中,它会知道派生类型是什么,但仅从功能签名中就没有办法解决这个问题。这意味着在生成的Java somewhere 中,必须要调用new Interface,如果Interface在Java端是抽象的,这将是不可能/合法的

  3. 可能的解决方法

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

    1. 手动将接口编写为适当的Java接口:

      public interface Interface {
          public String foo();
      }
      
    2. 修改SWIG接口文件:

      1. 在Java端将C ++类Interface重命名为NativeInterface。 (我们应该只对有问题的包提供它,我们的包裹代码生活在一个独立的包中,以避免人们做“疯狂”的事情。
      2. 我们在C ++代码中的任何地方都有Interface SWIG现在将使用NativeInterface作为Java端的类型。我们需要使用类型映射将函数参数中的NativeInterface映射到我们手动添加的Interface Java接口。
      3. NativeInterface标记为实现Interface,以使Java端行为自然且对Java用户可信。
      4. 我们需要提供一些额外的代码,这些代码可以作为实现Java Interface而不是NativeInterface的事物的代理。
      5. 我们传递给C ++的内容必须始终是NativeInterface,但并非所有Interface都是一个(虽然所有NativeInterfaces都会),所以我们提供了一些粘合剂{ {1}}表现为Interface,并且应用该胶水的类型图。 (有关NativeInterfaces
      6. 的讨论,请参阅this document

        这会产生一个模块文件,现在看起来像:

        pgcppname

        现在我们可以包装一个类似的函数:

        %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);
          }
        %}
        

        并使用它:

        // %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;
          }
        %}
        

        现在按照您的希望运行:

          

        ajw @ rapunzel:〜/ code / scratch / swig / javaintf&gt; java运行
          来自Java的你好!
          你好C ++

        我们已经将来自C ++的抽象类作为Java中的接口包装,就像Java程序员所期望的那样!