2013-11-06 1 views
1

Я пишу приложение, предназначенное для работы на разных платформах. Моя базовая библиотека написана на C++, и я хотел бы использовать SWIG для создания кода для конкретной платформы (Java/Android, C#/Windows, Objective C/iOS).Как реализовать общий интерфейс между C++ и Java с помощью SWIG и Downcasts?

Здесь я использую Message-Objects, которые я хотел бы передать из Java -> C++ и из C++ -> Java. Идея заключается в том, чтобы иметь базовый тип «CoreMessage» и производные сообщения «StatusMessage», «DebugMessage» ...

Для меня это кажется естественным сохранить интерфейс родовое делать что-то вроде этого:

C++ :

class CoreMessage { 

} 

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

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

The (двунаправленная) код интерфейса в 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 кодекса, вызов C++ через JNI и SWIG сгенерированного обертке:

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

Итак, для меня кажется, что JNI теряет информацию о типе при передаче объекта с одного языка на другой ...

Я прочитал некоторые темы и статьи, но я не нашел для этого решения - мне нужно двунаправленное решение.

Я не уверен, что «директор» - это не то, что я ищу? Насколько я понял директоров, они сделаны для классов, расширенных в java вручную и позволяют UP-cast на соответствующий C++ BaseClass ... Но я не уверен в своем понимании директоров ;-)

+0

Если вы создаете класс как тип CoreMessage, то это только тот тип. Однако, если он создан как тип StatusMessage или DebugMessage, то его оба типа и CoreMessage. Можете ли вы привести пример того, что вы пытаетесь выполнить, и где он был создан? – Samhain

+0

Директора действительно нужны, если вы хотите унаследовать от класса на целевом языке. Это звучит так, как вы хотите. – bluedog

+0

Спасибо за ответы! Я отредактировал сообщение, чтобы прояснить мою проблему - так, возможно, у кого-то есть решение? –

ответ

0

ну, я наконец нашел способ решить эту проблему:

Ключ: Я ДОЛЖЕН использовать указатели (в моем случае boost :: shared: ptr).

Итак, мои сообщения (сообщения.ч) выглядеть следующим образом:

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

Тогда я должен был сказать Swig о shared_ptr с помощью прилагаемого шаблона для повышения интеллектуального -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 

Ну, что теперь мы получаем: Swig генерирует классы Java, которые очень похожи на те, которые мы имели раньше. Но есть одна маленькая вещь, которая делает разницу: Конструкторы выглядеть следующим образом:

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

и волшебная линия супер (tscoreapiJNI.StatusMessage_SWIGSmartPtrUpcast (СРТК), правда); Эта строка обертывает динамическое преобразование указателя, которое позволяет отбрасывать ваш объект с Java.

Но: Это не так просто, как это может быть (возможно, я просто didn't найти решение ?!)

Вы не можете использовать 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); 
+0

Абсолютно. Вы можете легко перемещаться между ссылкой и указателем на C++ с помощью оператора address (&). – Samhain

+0

Еще лучше не обертывать dynamic_pointer_cast. Просто создайте boost :: dynamic_pointer_cast с шаблоном%. – EFraim

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

Надеюсь, что это поможет.