SWIG Java保留从C ++反弹的对象的类信息

好的,我有一个关键词,我故意远离标签和标题。 这是“Android”,但那是因为即使该项目是在Android中,我也不认为我的问题与它有任何关系,我不想吓唬没有Android经验的人。

所以,swig的常见问题。 我在C ++类中有一个虚方法,我通过在类中添加directorfunction使它在Java中可以重载,并且它可以工作。 问题是该方法接收的多态参数也在java端扩展,并且在Java中的虚方法调用期间,该对象带有所有多态信息被剥离。

提出确切的情况; 我正在用C ++编写游戏引擎,我想在Java中愉快地使用它。 游戏引擎有一个GameObject类,它注册CollisionListener ,当碰撞引擎检测到碰撞事件时,它会调用所有已注册的collisionListenercollidedWith(GameObject & collidee)方法,并将它们与它们碰撞的对象一起传递。

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

我正在使用以下接口文件Bridge.i将此类与GameObject类一起公开给java

 %module(directors="1") Bridge %feature("director") CollisionListener; %include "CollisionListener"; %feature("director") GameObject; %include "GameObject.h" 

现在,当我从java中的CollisionListenerinheritance并重载collidedWith ,它会被一个java端GameObject对象调用。 例如,如果我从java端inheritanceGameObject类并定义一个Bullet类,当这个项目符号与另一个带有监听器的对象collidedWith ,在collidedWith方法调用中,我收到的只是一个裸GameObject (object instanceof Bullet) ,因此(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 master可以帮助我对swig生成的文件进行修改。

我的小任务是在每个C ++端GameObject对象中保留一个jobject * self ,并在构造真正的java端GameObject期间分配真实java对象的地址(而不是仅仅包装指针的那个)。 这样,我可以在C ++端getSelf定义一个多态getSelf方法,并在java中愉快地使用结果。 有没有办法将必要的代码注入swig生成的文件?

谢谢

注意:如果您在Android上尝试过导演但他们没有工作,那是因为当前的稳定版本不支持它。 从swig网站下载Bleeding Edge。 但我是在2012年3月22日写的,很快就不需要这个说明了。 析构函数不是虚拟的原因是Bleeding Edge版本使析构函数中的程序崩溃,并使其成为非虚拟似乎现在可以控制它。

我已经为这个问题制定了解决方案。 这不是你在问题中提出的解决方案,它在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实际上使问题显而易见。

我使用的技巧是在创建时自动在HashMap注册GameObject所有Java派生实例。 然后在调度导演时调用它只是在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> instances = new HashMap>(); 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 typemap)。