2012-03-22 4 views
8

Ok, hay una palabra clave que he mantenido intencionalmente lejos de las etiquetas y el título. Eso es "Android", pero eso se debe a que aunque el proyecto está en Android, no creo que mi pregunta tenga nada que ver con eso, y no quiero asustar a personas sin experiencia en Android.SWIG Java Retaining Class información de los objetos que saltan de C++

Por lo tanto, el problema habitual con swig. Tengo un método virtual en una clase de C++, que lo he hecho supercargable en Java agregando la característica director a la clase y funciona. El problema es que el método recibe un argumento polimórfico que también se extiende en Java, y durante la llamada al método virtual en Java, el objeto viene con toda la información polimórfica eliminada.

Para presentar la situación exacta; Estoy escribiendo un motor de juego en C++, y quiero usarlo felizmente en Java. El motor del juego tiene una clase GameObject, que registra CollisionListener sy cuando el motor de colisión detecta un evento de colisión, llama al método collidedWith(GameObject & collidee) de todos los collisionListener registrados al pasarlos con el objeto que colisionaron.

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

estoy exponiendo esta clase, junto con la clase GameObject a Java utilizando el siguiente archivo de interfaz Bridge.i

%module(directors="1") Bridge 

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

Ahora, cuando heredar de CollisionListener en Java y la sobrecarga collidedWith, se consigue de llamada con un objeto java GameObject lateral. Por ejemplo, si heredo de la clase Java GameObject y defino una clase Bullet, cuando esta viñeta colisiona con otro objeto con un detector, en la llamada al método collidedWith, todo lo que recibo es GameObject, por lo que (object instanceof Bullet) no funciona. No es de sorprender, he excavado en la trago generado BridgeJNI.java y encontramos este:

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

Por lo tanto, se ajusta un nuevo objeto alrededor del puntero antes de llamar a las sobrecargas de Java.

Entonces, la pregunta principal es ¿cómo recibir un objeto Bullet cuando hay una colisión?

He encontrado una manera de lograrlo fácilmente, pero tengo que modificar los archivos generados automáticamente, lo cual es una mala idea. Así que estoy esperando que algún maestro de tragos me ayude a inyectar las modificaciones en los archivos generados por el trago.

Mi pequeño truco es mantener un jobject * self en cada objeto de C++ lado GameObject, y asigne la dirección del objeto java real durante la construcción de la banda de Real Java GameObject (y no el que meramente se ajusta el puntero). De esta forma, podría definir un método polimórfico getSelf en el lado C++ GameObject y utilizar el resultado felizmente en java. ¿Hay alguna forma de inyectar el código necesario en los archivos generados swig?

Gracias

Nota: Si ha intentado directores en Android y no han trabajado, es porque la versión estable actual no lo soporta. Descarga el Bleeding Edge del sitio web de swig. Pero escribo esto el 22/03/2012 y esta nota pronto será innecesaria. La razón por la cual el destructor no es virtual es porque la versión de Bleeding Edge hace que el programa falle en el destructor, y hacerlo no virtual parece mantenerlo bajo control por el momento.

+0

Entonces, ¿la versión corta de su pregunta es que quiere (por ejemplo) derivar 'GameObject' en Java y aún poder lanzar dentro de Java cuando ese tipo derivado pasa a una implementación Java de' collidedWith'? Seguro que tu pequeño truco puede ser envuelto en un mapa de tipos si ese es el caso. – Flexo

+0

¡Exactamente! Pensé que el trago tendría una forma de inyectar código, pero soy bastante nuevo para tragar. Comprobaré los mapas de tipos. – enobayram

+0

Voy a escribir una respuesta mañana con suerte entonces. – Flexo

Respuesta

8

He creado una solución a este problema. Sin embargo, no es la solución que sugirió en su pregunta, es más código en el lado de Java y no más en el lado de JNI/C++. (Encontré hacerlo de la forma en que sugeriste que es bastante complicado acertar en todos los casos posibles).

Simplifiqué sus clases a un fichero de cabecera única:

class GameObject { 
}; 

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

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

que también añadió makeCall para hacer realidad el problema obvio.

El truco que se utiliza es para registrar todas las instancias de Java derivados de GameObject en un HashMap automáticamente en el momento de la creación. Luego, al enviar la llamada directora, solo es cuestión de buscarla en HashMap.

A continuación, el archivo de módulo:

%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" 

Nota que desde todas las instancias de Java se están almacenando en un HashMap tenemos que utilizar un WeakReference para asegurarse de que no estamos prolongando sus vidas y la prevención de recogida de basuras sucediendo. Si le importan los subprocesos, agregue la sincronización según corresponda.

He probado esto con:

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

Dónde JCollisionListener es:

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

y JGameObject es:

public class JGameObject extends GameObject { 
} 

(Para referencia, si usted quería hacer el otro enfoque que estaría buscando escribir un tipo de mapa directorin).

+0

¡Guau! Tanto esfuerzo y una gran respuesta. Esto parece resolver mi problema, además de enseñarme mucho sobre el trago. Parece que a veces necesitamos anular la mitad del trago para obtener lo que queremos. Muchas gracias. – enobayram

+0

Lo bueno de SWIG es que te permite hacer este tipo de cosas cuando quieras, pero lo encapsula todo para que solo tengas que trabajar en los bits "impares" y todo el trabajo pesado se hace por ti. – Flexo

+0

La toma de decisiones interminable de los ingenieros de software: "¿Dónde debería invertir mi tiempo?". SWIG parece ser un buen negocio ya que su aproximación al problema de lenguaje cruzado parece ser lo más general posible. Es decir, como un generador de código externo, está libre de la mayoría de las limitaciones que sufren las soluciones puramente basadas en bibliotecas (como boost python). – enobayram

Cuestiones relacionadas