SWIG Java Retaining Class информация об объектах, возвращающихся из C++

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

Итак, обычная проблема с swig. У меня есть виртуальный метод в классе С++, который я сделал перегружаемым в 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, он вызывается с объектом GameObject на стороне java. Например, если я наследую класс GameObject со стороны Java и определяю класс Bullet, когда эта пуля сталкивается с другим объектом с прослушивателем, в вызове метода collidedWith все, что я получаю, — это голый GameObject, так что (object instanceof Bullet) не работает. Неудивительно, я покопался в сгенерированном swig 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 в каждом объекте GameObject стороны C++ и назначить адрес реального объекта java во время создания реальной стороны java GameObject (а не той, которая просто обертывает указатель). Таким образом, я мог бы определить полиморфный метод getSelf на стороне C++ GameObject и успешно использовать результат в java. Есть ли способ внедрить необходимый код в сгенерированные файлы swig?

Спасибо

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


person enobayram    schedule 22.03.2012    source источник
comment
Итак, короткая версия вашего вопроса: вы хотите иметь возможность (например) получить GameObject в Java и по-прежнему иметь возможность приводить в Java, когда этот производный тип передается в реализацию Java collidedWith? Почти уверен, что ваш маленький хак может быть обернут в карту типов, если это так.   -  person Flexo    schedule 23.03.2012
comment
Точно! Я думал, что у swig есть способ вводить код, но я новичок в swig. Я проверю карты шрифтов.   -  person enobayram    schedule 23.03.2012
comment
Завтра, надеюсь, напишу ответ.   -  person Flexo    schedule 23.03.2012


Ответы (1)


Я собрал решение этой проблемы. Это не совсем то решение, которое вы предложили в своем вопросе, это больше кода на стороне 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).

person Flexo    schedule 27.03.2012
comment
Ух ты! Столько усилий и отличный ответ. Это, кажется, решает мою проблему, а также учит меня многому о swig. Похоже, нам иногда нужно переопределить половину глотка, чтобы получить то, что мы хотим. Большое спасибо. - person enobayram; 28.03.2012
comment
Преимущество SWIG в том, что он позволяет вам делать подобные вещи, когда вы хотите, но инкапсулирует все это так, что вам нужно работать только с нечетными частями, а вся черновая работа выполняется за вас. - person Flexo; 28.03.2012
comment
Бесконечное принятие решений инженерами-программистами: во что мне инвестировать свое время? SWIG кажется хорошей сделкой, поскольку его подход к межъязыковой проблеме кажется настолько общим, насколько это возможно. Я имею в виду, что как внешний генератор кода он свободен от большинства ограничений, от которых страдают чисто библиотечные решения (например, boost python). - person enobayram; 28.03.2012
comment
5 лет спустя я пересмотрел это и понял, что есть, пожалуй, более аккуратное решение, которое не требует дополнительного хранилища: - person Flexo; 22.02.2017