Question

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 ;-)

Était-ce utile?

La solution

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);

Autres conseils

Note that the SWIG documentation has an official solution:

http://www.swig.org/Doc1.3/Java.html#adding_downcasts

Briefly speaking, you can add a static method to your derived type by:

%exception Ambulance::dynamic_cast(Vehicle *vehicle) {
$action
    if (!result) {
        jclass excep = jenv->FindClass("java/lang/ClassCastException");
        if (excep) {
            jenv->ThrowNew(excep, "dynamic_cast exception");
        }
    }
}
%extend Ambulance {
    static Ambulance *dynamic_cast(Vehicle *vehicle) {
        return dynamic_cast<Ambulance *>(vehicle);
    }
};

and use it in java like this:

Ambulance ambulance = Ambulance.dynamic_cast(vehicle);
ambulance.sound_siren();

Hope it helps.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top