使用SWIG生成Java接口

问题描述 投票:21回答:1

我正在使用SWIG制作C ++库的Java包装程序(关于Json(反)序列化),以便在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生成有效的接口?

java c++ java-native-interface swig
1个回答
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);
    }
  }
%}

这里我们为整个模块启用了Director,然后要求将它们专门用于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> Java运行Java您好!

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

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

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

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

    Interface *find_interface();
    

    这里SWIG对返回类型的了解比对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都将是),所以我们提供了一些胶合剂以使Interface的行为像NativeInterfaces ]和一个类型图以应用该胶水。 (有关this document的讨论,请参见pgcppname
    6. 这将导致一个模块文件,现在看起来像:

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

并像这样使用它:

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运行您好,Java!您好,C ++

而且我们完全像Java程序员所期望的那样,将C ++中的抽象类包装为Java中的接口!

© www.soinside.com 2019 - 2024. All rights reserved.