I´m writing an application which is intended to run on different platforms. My base library is written in C++ and I´d like to use SWIG for generating platform-specific code (Java/Android, C#/Windows, Objective C/iOS).
Here I´m using Message-Objects which I´d like to pass from Java -> C++ and from C++ -> Java. The idea is, to have a base type "CoreMessage" and derived messages "StatusMessage", "DebugMessage"...
For me it seems natural to keep the interface generic by doing something like this:
C++:
class CoreMessage {
}
class StatusMessage : public CoreMessage {
public:
string getStatusMessage();
}
class DebugMessage : public CoreMessage {
public:
... some stuff
}
The (bidirectional) interface code in C++ should look like this:
CoreMessage waitForNextMessage(); // Java calls this and blocks until new message is available in a message queue
void processMessage(CoreMessage m); // Java calls this and puts a new message in an eventhandling mechanism
My Swig file is very basic, it simply includes the class-definitions and the interface - nothing else.
Now Swig generates all the classes and interfaces for Java, including the inheritance for the messages in Java:
public class CoreMessage {
...
}
public class StatusMessage extends CoreMessage {
...
}
public class DebugMessage extends CoreMessage {
...
}
The main Java-Module now looks like this:
public native void processMessage(CoreMessage);
public native CoreMessage waitForNextMessage();
When I try to call this code from Java like that:
StatusMessage m = new StatusMessage();
processMessage(m);
this code in C++ will be executed and causes an error:
void processMessage(CoreMessage in) {
StatusMessage* statusPtr = dynamic_case<StatusMessage*>(&in); // works
StatusMessage status = *(statusPtr); // This will cause a runtime error
}
Same problem in the other direction: C++ Code
TsCoreMessage waitForMessage() {
StatusMessage m = StatusMessage();
return m;
}
with this Java Code, calling C++ via JNI and SWIG-generated wrapper:
CoreMessage msg = waitForMessage();
// msg instanceof StatusMessage returns false
StatusMessage s = (StatusMessage) msg; // Causes java.lang.ClassCaseException
So for me it seems JNI looses the type information when passing the object from one language to another...
I did read some threads and articles but I did not find a solution for this - I need a bidirectional solution.
I´m not sure "director" is not what I´m looking for? As far as I understood directors, they are made for classes, extended in java by hand and allow to UP-cast to the corresponding C++ BaseClass... But I´m not sure with my understanding of directors ;-)
well, I finally found a way to solve this:
The key is: I MUST use pointers (in my case boost::shared:ptr).
So my Messages (messages.h) now look like this:
typedef boost::shared_ptr<CoreMessage> CoreMessagePtr;
public class CoreMessage {
...
}
typedef boost::shared_ptr<StatusMessage> StatusMessagePtr;
public class StatusMessage extends CoreMessage {
...
}
typedef boost::shared_ptr<DebugMessage> DebugMessagePtr;
public class DebugMessage extends CoreMessage {
...
}
And the functions also need to be changed to these Pointers:
CoreMessagePtr waitForNextMessage();
void processMessage(CoreMessagePtr m);
Then, I had to tell Swig about the shared_ptr using the included template for boost smart-pointers:
%module myapi
%include <boost_shared_ptr.i> // Tells Swig what to do with boost::shared_ptr
%header %{ // Tells Swig to add includes to the header of the wrapper
#include <boost/shared_ptr.hpp>
#include "messages.h"
%}
// Now here is the magic: Tell Swig that we want to use smart-Pointers for
// these classes. It makes Swig generate all necessary functions and wrap
// away all the de-referencing.
%shared_ptr(CoreMessage)
%shared_ptr(StatusMessage)
%shared_ptr(DebugMessage)
%include "message.h" // Finally: Tell Swig which interfaces it should create
Well, what we now get: Swig generates Java classes which look very similar to the ones we had before. But there is one small thing which makes the difference: The constructors now look like this:
protected StatusMessage(long cPtr, boolean cMemoryOwn) {
super(tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast(cPtr), true);
swigCMemOwnDerived = cMemoryOwn;
swigCPtr = cPtr;
}
and the magic line is super(tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast(cPtr), true); This line wraps an dynamic cast of the pointer which enables casting your object from Java.
But: It is not as easy as it might be (perhaps I just didn´t find a solution?!)
You cannot use Java casting to do the work like this [Java Code]:
CoreMessage m = waitForMessage(); // Returns a StatusMessage
StatusMessage mm = (StatusMessage) m; // Throws ClassCaseException
This is because java cannot do the dynamic cast which is necessary but needs the C++ code for that. My solution looks like this and uses a function template:
I added this template to messages.h
template <class T>
static boost::shared_ptr<T> cast(TsCoreMessagePtr coreMsg) {
return boost::dynamic_pointer_cast<T>(coreMsg);
}
Now tell Swig how to handle this template [added to the end of interface.i]:
%template(castStatusMessage) cast<StatusMessage>;
%template(castDebugMessage) cast<DebugMessage>;
What happens is: Swig adds these two functions to myapi.java:
public static StatusMessage castStatusMessage(CoreMessage coreMsg) {
long cPtr = myapiJNI.castStatusMessage(CoreMessage.getCPtr(coreMsg), coreMsg);
return (cPtr == 0) ? null : new StatusMessage(cPtr, true);
}
public static DebugMessage castDebugMessage(CoreMessage coreMsg) {
long cPtr = myapiJNI.castDebugMessage(CoreMessage.getCPtr(coreMsg), coreMsg);
return (cPtr == 0) ? null : new DebugMessage(cPtr, true);
}
And finally you can use it like this in your java code:
CoreMessage m = waitForMessage(); // Returns a StatusMessage
StatusMessage mm = myapi.cast(m);