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;
};
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;
};
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);
}
}
How can I generate a valid interface with SWIG ?
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".
Implementing a C++ interface in Java
Given a header file (
module.hh
):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:
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:We can then run this and see it's working as expected:
If you're happy with it being neither
abstract
nor aninterface
you can stop reading here, directors do everything you need.Why does SWIG generate a
class
instead of aninterface
?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? Theclass
isn't evenabstract
, let alone aninterface
(See point 4 here), which would feel more natural on the Java side. The answer is twofold: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:
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 tonew Interface
, which wouldn't be possible/legal ifInterface
were abstract on the Java side.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:
Manually write the interface as a proper Java interface:
Modify the SWIG interface file:
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.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.NativeInterface
as implementingInterface
to make the Java side behaviour natural and believable to a Java user.Interface
without being aNativeInterface
too.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:
Now we can wrap a function like:
and use it like:
This now runs as you'd hope:
And we've wrapped an abstract class from C++ as an interface in Java exactly as a Java programmer would expect!