如何使用SWIG和Downcast实现C ++和Java之间的通用接口?

时间:2013-11-06 14:42:21

标签: java-native-interface swig downcast

我正在编写一个旨在在不同平台上运行的应用程序。 我的基础库是用C ++编写的,我想使用SWIG生成特定于平台的代码(Java / Android,C#/ Windows,Objective C / iOS)。

这里我使用的是我希望从Java传递的Message-Objects - > C ++和C ++ - > Java的。这个想法是,有一个基本类型“CoreMessage”和派生消息“StatusMessage”,“DebugMessage”......

对我而言,通过这样做来保持界面通用似乎很自然:

C ++:

class CoreMessage {

}

class StatusMessage : public CoreMessage {
public:
     string getStatusMessage();
}

class DebugMessage : public CoreMessage {
public:
     ... some stuff
}

C ++中的(双向)接口代码应如下所示:

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

我的Swig文件非常基础,它只包含类定义和界面 - 没有别的。

现在,Swig为Java生成所有类和接口,包括Java中消息的继承:

public class CoreMessage {
    ...
}
public class StatusMessage extends CoreMessage {
    ...
}
public class DebugMessage extends CoreMessage {
    ...
}

主Java-Module现在看起来像这样:

public native void processMessage(CoreMessage);
public native CoreMessage waitForNextMessage();

当我尝试从Java调用此代码时:

StatusMessage m = new StatusMessage();
processMessage(m);

这个C ++代码将被执行并导致错误:

void processMessage(CoreMessage in) {
    StatusMessage* statusPtr = dynamic_case<StatusMessage*>(&in); // works
    StatusMessage status = *(statusPtr); // This will cause a runtime error
}

另一个方向的相同问题:C ++代码

TsCoreMessage waitForMessage() {
    StatusMessage m = StatusMessage();
    return m;
}

使用此Java代码,通过JNI和SWIG生成的包装器调用C ++:

CoreMessage msg = waitForMessage();
// msg instanceof StatusMessage returns false
StatusMessage s = (StatusMessage) msg; // Causes java.lang.ClassCaseException

所以对我而言,当将对象从一种语言传递给另一种语言时,似乎JNI会丢失类型信息......

我确实阅读了一些主题和文章,但我没有找到解决方案 - 我需要一个双向解决方案。

我不确定“导演”不是我要找的东西? 据我了解导演,他们是为类而编写的,手工扩展java并允许UP-cast到相应的C ++ BaseClass ......但我不确定我对导演的理解; - )

2 个答案:

答案 0 :(得分:0)

好吧,我终于找到了解决这个问题的方法:

关键是:我必须使用指针(在我的例子中是boost :: shared:ptr)。

所以我的消息(messages.h)现在看起来像这样:

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 {
    ...
}

并且还需要将这些功能更改为这些指针:

CoreMessagePtr waitForNextMessage();
void processMessage(CoreMessagePtr m);

然后,我不得不使用boost智能指针的附带模板告诉Swig有关shared_ptr的信息:

%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

嗯,我们现在得到的:Swig生成的Java类与我们以前的类非常相似。但是有一件小事可以产生不同:构造函数现在看起来像这样:

protected StatusMessage(long cPtr, boolean cMemoryOwn) {
    super(tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast(cPtr), true);
    swigCMemOwnDerived = cMemoryOwn;
    swigCPtr = cPtr;
}

并且魔术线是超级的(tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast(cPtr),true); 该行包装指针的动态转换,它允许从Java转换对象。

但是:它并不像它可能那么容易(也许我只是找不到解决方案?!)

您无法使用Java强制转换来完成[Java Code]的工作:

CoreMessage m = waitForMessage(); // Returns a StatusMessage
StatusMessage mm = (StatusMessage) m; // Throws ClassCaseException

这是因为java无法进行必要的动态强制转换但需要C ++代码。我的解决方案看起来像这样并使用函数模板:

我将此模板添加到messages.h

template <class T>
static boost::shared_ptr<T> cast(TsCoreMessagePtr coreMsg) {
    return boost::dynamic_pointer_cast<T>(coreMsg);
}

现在告诉Swig如何处理这个模板[添加到interface.i的末尾]:

%template(castStatusMessage) cast<StatusMessage>;
%template(castDebugMessage) cast<DebugMessage>;

会发生什么:Swig将这两个函数添加到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);
}

最后你可以在java代码中使用它:

CoreMessage m = waitForMessage(); // Returns a StatusMessage
StatusMessage mm = myapi.cast(m);

答案 1 :(得分:0)

请注意,SWIG文档有一个官方解决方案:

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

简而言之,您可以通过以下方式为派生类型添加静态方法:

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

并在java中使用它:

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

希望它有所帮助。