使用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方面会更自然。 答案是双重的:
- SWIG提供了在Java端调用
delete
,操作cPtr
等的机制。 根本无法在interface
中完成。 -
考虑我们包装以下函数的情况:
Interface *find_interface();
在这里,SWIG对返回类型的了解不仅仅与
Interface
类型有关。 在一个理想的世界中,它会知道派生类型是什么,但仅从function签名中就没有办法解决这个问题。 这意味着在生成的Java 某处 ,必须要调用new Interface
,如果Interface
在Java端是抽象的,这将是不可能/合法的。
可能的解决方法
如果您希望将此作为接口提供,以便在Java中表达具有多重inheritance的类型层次结构,那么这将是非常有限的。 但是有一个解决方法:
-
手动将接口编写为适当的Java接口:
public interface Interface { public String foo(); }
-
修改SWIG接口文件:
- 在Java端将C ++类
Interface
重命名为NativeInterface
。 (我们应该只对有问题的包提供它,我们的包装代码生活在一个独立的包中,以避免人们做“疯狂”的事情。 - 我们在C ++代码中都有一个
Interface
SWIG现在将使用NativeInterface
作为Java端的类型。 我们需要使用类型映射将函数参数中的NativeInterface映射到我们手动添加的Interface
Java接口。 - 将
NativeInterface
标记为实现Interface
以使Java端行为自然且对Java用户可信。 - 我们需要提供一些额外的代码,这些代码可以作为实现Java
Interface
的代理,而不是NativeInterface
。 - 我们传递给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); } %} - 在Java端将C ++类
现在我们可以包装一个函数:
// %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程序员所期望的那样!