使用SWIG生成Java接口

Mar*_*say 21 c++ java 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; 
};
Run Code Online (Sandbox Code Playgroud)

现在,我正在尝试从这个类生成一个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;
};
Run Code Online (Sandbox Code Playgroud)

但是生成的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);
  }  

}
Run Code Online (Sandbox Code Playgroud)

如何使用SWIG生成有效的界面?

Fle*_*exo 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;
}
Run Code Online (Sandbox Code Playgroud)

我们想把它包装起来,让它从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);
    }
  }
%}
Run Code Online (Sandbox Code Playgroud)

在这里,我们为整个模块启用了导向器,然后请求将它们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!";
  }
}
Run Code Online (Sandbox Code Playgroud)

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

ajw @ rapunzel:〜/ code/scratch/swig/javaintf> java
从Java 运行Hello!

如果你喜欢它既不abstract也不是interface你可以停止阅读这里,导演你需要的一切.

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

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

  1. SWIG提供在Java端调用delete,操作cPtr等的机制.根本无法做到这一点interface.
  2. 考虑我们包装以下函数的情况:

    Interface *find_interface();
    
    Run Code Online (Sandbox Code Playgroud)

    这里痛饮知道没有什么更多的返回类型比,它的类型Interface.在理想的世界中,它会知道派生类型是什么,但仅从功能签名中就没有办法解决这个问题.这意味着在生成的Java 某处必须要进行调用new Interface,如果Interface在Java端是抽象的,这将是不可能/合法的.

可能的解决方法

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

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

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

    1. 重命名C++类InterfaceNativeInterface在Java端.(我们应该只对有问题的包提供它,我们的包装代码生活在一个独立的包中,以避免人们做"疯狂"的事情.
    2. 我们Interface在C++代码中的每个地方都将使用SWIG NativeInterface作为Java端的类型.我们需要使用类型映射将NativeInterface函数参数映射到Interface我们手动添加的Java接口.
    3. 标记NativeInterface为实现Interface使Java端行为自然且对Java用户可信.
    4. 我们需要提供一些额外的代码,这些代码可以作为实现Java的东西的代理,Interface而不是一样NativeInterface.
    5. 我们传递给C++的东西必须始终是一个NativeInterface静止的,不是所有Interface的都是一个(尽管一切NativeInterfaces都会),所以我们提供了一些胶水来使其Interface表现为NativeInterfaces,并且一个类型图应用了这个胶水.(有关讨论,请参阅此文档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);
      }
    %}
    
    Run Code Online (Sandbox Code Playgroud)

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

// %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;
  }
%}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

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!";
  }
}
Run Code Online (Sandbox Code Playgroud)

这现在按照您的希望运行:

ajw @ rapunzel:〜/ code/scratch/swig/javaintf> java
从Java 运行Hello!
你好,来自C++

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

  • 正是我需要的,所以解释得很好!非常感谢. (4认同)
  • 我认为SWIG 3.1在发布时可能会大大改变这个答案:https://github.com/swig/swig/blob/master/Lib/java/swiginterface.i (3认同)