使用SWIG生成Java接口

我正在使用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生成有效的界面?

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

用Java实现C ++接口

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

 #include  #include  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  #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> java Run
来自Java的你好!

如果你对它既不abstract也不满意,你可以在这里停止阅读,那么导演会做你需要的一切。

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

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

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

     Interface *find_interface(); 

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

可能的解决方法

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

  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都会),所以我们提供了一些NativeInterfaces来使Interface表现为NativeInterfaces ,以及一个类型图来应用这个NativeInterfaces 。 (有关pgcppname的讨论,请参阅此文档 )

    这导致模块文件现在看起来像:

     %module(directors="1") test %{ #include  #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 things = new ArrayList(); // 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
来自Java的你好!
你好,来自C ++

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