1

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

在这里,我正在使用我想从 Java -> 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 模块现在看起来像这样:

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中手动扩展并允许向上转换到相应的C++ BaseClass ...但我不确定我对导演的理解;-)

4

2 回答 2

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

魔术线是 super(tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast(cPtr), true); 这一行包装了指针的动态转换,它可以从 Java 中转换您的对象。

但是:这并不像想象的那么容易(也许我只是没有找到解决方案?!)

您不能使用 Java 强制转换来完成这样的工作 [Java 代码]:

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);
于 2013-11-07T12:27:00.247 回答
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();

希望能帮助到你。

于 2016-09-05T12:05:58.833 回答