SWIG Java Сохранение информации о классе объектов, подпрыгивающих от C ++ - PullRequest
9 голосов
/ 22 марта 2012

Хорошо, есть ключевое слово, которое я намеренно скрывал от тегов и заголовка.Это «Android», но это потому, что, хотя проект находится в Android, я не думаю, что мой вопрос имеет какое-либо отношение к нему, и я не хочу пугать людей без опыта в Android.

Итак, обычная проблема с глотком.У меня есть виртуальный метод в классе C ++, который я сделал перегружаемым в Java, добавив в класс функцию director, и она работает.Проблема заключается в том, что метод получает полиморфный аргумент, который также расширяется на стороне Java, и во время вызова виртуального метода в Java объект поставляется со всей извлеченной полиморфной информацией.

Чтобы представить точную ситуацию;Я пишу игровой движок на C ++ и хочу с удовольствием использовать его на Java.У игрового движка есть класс GameObject, который регистрирует CollisionListener с, и когда механизм столкновений обнаруживает событие столкновения, он вызывает метод collidedWith(GameObject & collidee) всех зарегистрированных collisionListener с, передавая им объект, с которым они столкнулись.

class CollisionListener {
public:
    virtual bool collidedWith(GameObject &){};
    ~CollisionListener(){} // I know this needs to be virtual but let's forget about that now
};

Я представляю этот класс вместе с классом GameObject для java, используя следующий файл интерфейса Bridge.i

%module(directors="1") Bridge

%feature("director") CollisionListener;
%include "CollisionListener";
%feature("director") GameObject;
%include "GameObject.h"

Теперь, когда я наследую от CollisionListener в java и перегрузке collidedWith, он вызывается с объектом java side GameObject.Например, если я наследую от класса GameObject на стороне java и определю класс Bullet, когда эта пуля сталкивается с другим объектом со слушателем, в вызове метода collidedWith все, что я получаю, - это GameObject, так что (object instanceof Bullet) не работает.Не удивительно, я копался в сгенерированном BridgeJNI.java и нашел следующее:

  public static boolean SwigDirector_CollisionListener_collidedWith(CollisionListener self, long arg0) {
    return self.collidedWith(new GameObject(arg0, false));
  }

Так что он оборачивает новый объект вокруг указателя перед вызовом java-перегрузок.

Итак,главный вопрос - как получить объект Bullet при столкновении?

Я нашел способ легко добиться этого, но мне нужно изменить автоматически сгенерированные файлы, что плохоидея.Поэтому я надеюсь, что какой-нибудь мастер swig сможет помочь мне внести изменения в файлы, сгенерированные swig.

Мой маленький взлом состоит в том, чтобы сохранить jobject * self в каждом объекте C ++ на стороне GameObject и назначить адресреальный объект Java во время создания реальной стороны Java GameObject (а не тот, который просто оборачивает указатель).Таким образом, я мог бы определить полиморфный getSelf метод на стороне C ++ GameObject и счастливо использовать результат в Java.Есть ли способ внедрить необходимый код в файлы, созданные с помощью swig?

Спасибо

Примечание: Если вы пробовали режиссеры на Android, но они не работали, это потому, что текущая стабильная версия делаетне поддерживаю это.Загрузите Bleeding Edge с сайта Swig.Но я пишу это 22/03/2012, и эта заметка скоро будет ненужной.Причина, по которой деструктор не является виртуальным, заключается в том, что версия Bleeding Edge приводит к аварийному завершению программы в деструкторе, и, по-видимому, из-за того, что деструктор не виртуален, он пока находится под контролем.

1 Ответ

9 голосов
/ 27 марта 2012

Я собрал решение этой проблемы. Это не совсем то решение, которое вы предложили в своем вопросе, это больше кода на стороне Java и никаких дополнительных на стороне JNI / C ++. (Я нашел, что делать так, как вы предлагали, довольно сложно, чтобы исправить все возможные случаи).

Я упростил ваши классы до одного заголовочного файла:

class GameObject {
};

class CollisionListener {
public:
    virtual bool collidedWith(GameObject &) { return false; }
    virtual ~CollisionListener() {} 
};

inline void makeCall(GameObject& o, CollisionListener& c) {
    c.collidedWith(o);
}

, который также добавил makeCall, чтобы фактически сделать проблему очевидной.

Уловка, которую я использовал, заключается в автоматической регистрации всех производных от Java экземпляров GameObject в HashMap во время создания. Затем, при отправке вызова директора, это просто вопрос поиска его в HashMap.

Тогда файл модуля:

%module(directors="1") Test

%{
#include "test.hh"
%}

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("test");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

/* Pretty standard so far, loading the shared object 
   automatically, enabling directors and giving the module a name. */    

// An import for the hashmap type
%typemap(javaimports) GameObject %{
import java.util.HashMap;
import java.lang.ref.WeakReference;
%}

// Provide a static hashmap, 
// replace the constructor to add to it for derived Java types
%typemap(javabody) GameObject %{
  private static HashMap<Long, WeakReference<$javaclassname>> instances 
                        = new HashMap<Long, WeakReference<$javaclassname>>();

  private long swigCPtr;
  protected boolean swigCMemOwn;

  public $javaclassname(long cPtr, boolean cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
    // If derived add it.
    if (getClass() != $javaclassname.class) {
      instances.put(swigCPtr, new WeakReference<$javaclassname>(this));
    }
  }

  // Just the default one
  public static long getCPtr($javaclassname obj) {
    return (obj == null) ? 0 : obj.swigCPtr;
  }

  // Helper function that looks up given a pointer and 
  // either creates or returns it
  static $javaclassname createOrLookup(long arg) {
    if (instances.containsKey(arg)) {
      return instances.get(arg).get();
    }
    return new $javaclassname(arg,false);
  }
%}

// Remove from the map when we release the C++ memory
%typemap(javadestruct, methodname="delete", 
         methodmodifiers="public synchronized") GameObject {
  if (swigCPtr != 0) {
    // Unregister instance
    instances.remove(swigCPtr);
    if (swigCMemOwn) {
      swigCMemOwn = false;
      $imclassname.delete_GameObject(swigCPtr);
    }
    swigCPtr = 0;
  }
}

// Tell SWIG to use the createOrLookup function in director calls.
%typemap(javadirectorin) GameObject& %{
    $javaclassname.createOrLookup($jniinput)
%}
%feature("director") GameObject;

// Finally enable director for CollisionListener and include the header
%feature("director") CollisionListener;    
%include "test.hh"

Обратите внимание, что, поскольку все экземпляры Java хранятся в HashMap, нам нужно использовать WeakReference, чтобы быть уверенным, что мы не продлим их жизнь и не допустим сбор мусора. Если вам небезразличны потоки, добавьте синхронизацию соответствующим образом.

Я проверил это с:

public class main {
  public static void main(String[] argv) {
    JCollisionListener c = new JCollisionListener();
    JGameObject o = new JGameObject();
    c.collidedWith(o);  
    Test.makeCall(o,c);
  }
}

Где JCollisionListener:

public class JCollisionListener extends CollisionListener {
  public boolean collidedWith(GameObject i) {
    System.out.println("In collide");
    if (i instanceof JGameObject) {
       System.out.println("Is J");
    }
    else {
       System.out.println("Not j");
    }
    JGameObject o = (JGameObject)i;
    return false;
  }
}

и JGameObject:

public class JGameObject extends GameObject {
}

(Для справки, если вы хотите использовать другой подход, вам нужно написать directorin карту типа).

...