使用 SWIG 生成 Java 接口
我正在使用 SWIG 制作 C++ 库的 Java 包装器(关于 Json(反)序列化),以便在 Android 上使用它.我在 C++ 中定义了一个抽象类,代表一个可以(反)序列化的对象:
I'm using SWIG to make a Java wrapper of a C++ library (about Json (de)serialization) to use it on Android. I defined an abstract class in C++, representing an object which can be (de)serialized :
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
现在,我正在尝试从此类生成 Java 接口.这是我的 SWIG 界面:
Now, I'm trying to generate from this class a Java interface. Here's my SWIG interface:
%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 这是一个接口)一个简单的类,具有两个方法和一个默认构造函数/析构函数:
But the generated Java code is (obviously, as I was not able to find out how to tell SWIG that's an interface) a simple class, with the two methods and a default constructor/destructor:
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 生成有效接口?
How can I generate a valid interface with SWIG ?
推荐答案
您可以使用Directors",但是它并不像您希望的那样直接从 C++ 抽象类映射到 Java.因此,我的答案分为三个部分 - 首先是在 Java 中实现 C++ 纯虚函数的简单示例,其次是对输出为何如此的解释,第三是解决方法".
You can achieve what you're looking for with SWIG+Java using "Directors", however it's not quite as straightforward mapping from the C++ abstract classes onto Java as you might hope. My answer therefore is split into three parts - firstly the simple example of implementing a C++ pure virtual function in Java, secondly an explanation of why the output is like that and thirdly a "work-around".
给定一个头文件(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 接口来做到这一点:
We'd like to wrap this and make it work intuitively from the Java side. We can do this by defining the following SWIG interface:
%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.
" + e);
System.exit(1);
}
}
%}
这里我们为整个模块启用了导向器,然后请求它们专门用于class Interface
.除此之外和我最喜欢的自动加载共享对象"代码没有什么特别值得注意的.我们可以使用以下 Java 类对此进行测试:
Here we've enabled directors for the whole module, and then requested they be used for class Interface
specifically. Other than that and my favourite "load the shared object automatically" code there's nothing particularly noteworthy. We can test this with the following Java class:
public class Run extends Interface {
public static void main(String[] argv) {
test.bar(new Run());
}
public String foo() {
return "Hello from Java!";
}
}
然后我们可以运行它并查看它是否按预期工作:
We can then run this and see it's working as expected:
ajw@rapunzel:~/code/scratch/swig/javaintf > java 运行
来自 Java 的您好!
ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!
如果您对它既不是 abstract
也不是 interface
感到满意,您可以在此处停止阅读,导演会满足您的一切需求.
If you're happy with it being neither abstract
nor an interface
you can stop reading here, directors do everything you need.
然而,SWIG 将看似抽象的类变成了具体的类.这意味着在 Java 方面我们可以合法地编写 new Interface();
,这是没有意义的.SWIG 为什么要这样做?class
甚至不是 abstract
,更不用说 interface
(参见第 4 点 here),这在 Java 方面会感觉更自然.答案是双重的:
SWIG has however made what looked like an abstract class into a concrete one. That means on the Java side we could legally write new Interface();
, which makes no sense. Why does SWIG do this? The class
isn't even abstract
, let alone an interface
(See point 4 here), which would feel more natural on the Java side. The answer is twofold:
- SWIG 提供了在 Java 端调用
delete
、操作cPtr
等的机制.这根本无法在interface
中完成. 考虑我们包装以下函数的情况:
- SWIG supplies mechanics for calling
delete
, manipulating thecPtr
etc. on the Java side. That couldn't be done in aninterface
at all. Consider the case where we wrapped the following function:
Interface *find_interface();
这里 SWIG 对于返回类型的了解一无所知,而不仅仅是 Interface
类型.在理想的世界中,它会知道派生类型是什么,但是仅从函数签名中它无法弄清楚这一点.这意味着在生成的 Java somewhere 中必须调用 new Interface
,如果 Interface
在 Java 方面是抽象的.
Here SWIG knows nothing more about the return type than that it's of type Interface
. In an ideal world it would know what the derived type is, but from the function signature alone there's no way for it to figure this out. This means that in the generated Java somewhere there's going to have to be a call to new Interface
, which wouldn't be possible/legal if Interface
were abstract on the Java side.
可能的解决方法
如果您希望将其作为接口提供,以便在 Java 中表达具有多重继承的类型层次结构,这将是非常有限的.但是有一个解决方法:
Possible workaround
If you were hoping to provide this as an interface in order to express a type hierarchy with multiple inheritance in Java this would be quite limiting. There's a workaround however:
手动将接口写成正确的Java接口:
Manually write the interface as a proper Java interface:
public interface Interface {
public String foo();
}
修改 SWIG 接口文件:
Modify the SWIG interface file:
- 在 Java 端将 C++ 类
Interface
重命名为NativeInterface
.(我们也应该让它只对有问题的包可见,我们的包装代码存在于它自己的包中,以避免人们做疯狂"的事情. - 我们在 C++ 代码中到处都有
Interface
SWIG 现在将使用NativeInterface
作为 Java 端的类型.我们需要类型映射来将函数参数中的这个NativeInterface
映射到我们手动添加的Interface
Java 接口. - 将
NativeInterface
标记为实现Interface
以使 Java 端行为自然且对 Java 用户可信. - 我们需要提供一些额外的代码,这些代码可以作为实现 Java
Interface
而不是NativeInterface
的事物的代理. - 我们传递给 C++ 的必须始终是一个
NativeInterface
,但并非所有Interface
都是一个(尽管所有NativeInterfaces
都是),因此我们提供了一些胶水来使Interface
表现得像NativeInterfaces
,并提供了一个类型映射来应用该胶水.(有关pgcppname的讨论,请参阅 本文档代码>)
- Rename the C++ class
Interface
to beNativeInterface
on the Java side. (We ought to make it visible only to the package in question too, with our wrapped code living in a package of its own to avoid people doing "crazy" things. - Everywhere we have an
Interface
in C++ code SWIG will now be usingNativeInterface
as the type on the Java side. We need typemaps to map thisNativeInterface
in function parameters onto theInterface
Java interface we added manually. - Mark
NativeInterface
as implementingInterface
to make the Java side behaviour natural and believable to a Java user. - We need to supply a little bit of extra code that can act as a proxy for things which implement the Java
Interface
without being aNativeInterface
too. - What we pass to C++ must always be a
NativeInterface
still, not allInterface
s will be one though (although allNativeInterfaces
will), so we provide some glue to makeInterface
s behave asNativeInterfaces
, and a typemap to apply that glue. (See this document for a discussion of thepgcppname
)
这导致一个模块文件现在看起来像:
This results in a module file that now looks like:
%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);
}
%}
现在我们可以像这样包装一个函数:
Now we can wrap a function like:
// %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++ 的你好
ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!
Hello from C++
我们已经将 C++ 中的抽象类包装为 Java 中的接口,这与 Java 程序员所期望的完全一样!
And we've wrapped an abstract class from C++ as an interface in Java exactly as a Java programmer would expect!
相关文章