使用SWIG生成Java接口
我正在使用SWIG将C++库(关于Json(de)序列化)的Java封装器用于Android上。我在C++定义的一个抽象类,表示序列化的对象可以是(6):使用SWIG生成Java接口
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
现在,我试图从这个类生成的Java接口。这里是我痛饮接口:
%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所需的内容,但它不像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侧面直观地工作。整个模块
%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
明确:我们可以通过定义以下SWIG接口做到这一点。除此之外,我最喜欢的“自动加载共享对象”代码没有什么特别值得注意的。
public class Run extends Interface {
public static void main(String[] argv) {
test.bar(new Run());
}
public String foo() {
return "Hello from Java!";
}
}
然后我们就可以运行这个,看看它的工作如期望的那样:
AJW @莴苣:〜/代码/刮/痛饮/ javaintf> Java中,我们可以用下面的Java类测试这个运行
来自Java的你好!
如果你喜欢它既不abstract
也不是interface
你可以停止阅读这里,导演你需要的一切。
为什么SWIG生成class
而不是interface
?
然而,SWIG使得看起来像抽象类的东西变成了具体的东西。这意味着在Java方面我们可以合法编写new Interface();
,这是没有意义的。 SWIG为什么这样做? class
甚至不是abstract
,更不用说interface
(见第4点here),这在Java方面会感觉更自然。答案是双重的:
- SWIG提供调用
delete
的机制,在Java端操纵cPtr
等。这根本不能在interface
中完成。 -
考虑我们裹以下功能的情况下:
Interface *find_interface();
这里痛饮知道没有更多的返回类型比它的
Interface
类型。在一个理想的世界里,它会知道派生类型是什么,但是从函数签名本身来看,没有办法让它弄清楚。这意味着在生成的Java 的某个地方必须要调用new Interface
,如果Interface
在Java端是抽象的,这将不可能/合法。
可能的解决方法
如果你希望以表达对在Java中多重继承,这将是相当限制类型层次提供这作为一个接口。有不过解决方法:
-
手动编写接口作为一个适当的Java接口:
public interface Interface { public String foo(); }
-
修改痛饮接口文件:
- 重命名C++类
Interface
是NativeInterface
在Java方面。 (我们应该让它只对所涉及的软件包可见,我们的包装代码会自己包装以避免人们做“疯狂”的事情。使用NativeInterface
作为Java端类型。我们需要typemaps这个NativeInterface
在功能参数映射到我们手动添加的Interface
Java接口。 - 马克
NativeInterface
作为执行Interface
使Java端行为的自然和可信的Java用户 - 我们需要提供一些额外的代码,这些代码可以作为代理实现Java
Interface
而不是NativeInterface
。 - 我们传递到C什么++必须始终是
NativeInterface
不过,并非所有的Interface
旨意是一个虽然(尽管所有NativeInterfaces
会),所以我们提供了一些胶水,使Interface
小号表现为NativeInterfaces
,和类型映射到应用该胶水。(对于pgcppname
的讨论,请参阅this document)
这导致模块文件,现在看起来像:
%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); } %}
- 重命名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<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 @莴苣:〜/代码/刮/痛饮/ javaintf> Java运行
从Java 您好!
你好从C++
而且我们已经在Java中包裹从C++抽象类作为接口正是因为Java程序员所期望的!
为什么? Java已经有JSON API,只是使用其中的一种。 –
@ChrisDennett:我已经将这个库用于C++中的其他用法。在不久的将来我还有其他的图书馆,所以我也会遇到同样的问题。 –
我不明白 - 你想在这里产生什么? SWIG生成与您显示的声明和定义相匹配的代理,这就是它在这里完成的。这是[这个问题](http://www.swig.org/Doc1.3/Java.html#adding_downcasts)的情况吗?我可以给你一个具体的例子,如果你让你想要更清楚一点。 – Flexo