2012-03-22 53 views
8

好的,有一个关键字我有意远离标签和标题。这就是“Android”,但那是因为即使该项目在Android中,我认为我的问题与它没有任何关系,并且我不想吓唬没有Android经验的人。SWIG Java保留从C++弹起的对象的类信息

所以,通常与swig相关的问题。我在C++类中有一个虚拟方法,我通过在类中添加director功能使它在Java中可以重载,并且工作正常。问题是该方法接收到一个多态的参数,这个参数在java端也被扩展了,并且在Java中的虚拟方法调用期间,该对象带有全部的多态信息。

为了呈现确切的情况;我正在用C++编写游戏引擎,并且我想在Java中愉快地使用它。游戏引擎有一个GameObject类,其中注册了CollisionListener,并且当碰撞引擎检测到碰撞事件时,它调用所有注册的的collidedWith(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

%module(directors="1") Bridge 

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

现在揭露这个类,与GameObject类一起去渣,当我在Java和超载collidedWithCollisionListener继承,它得到的所谓用java端GameObject对象。例如,如果我继承了java类GameObject类并定义了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 master可以帮我将修改注入swig生成的文件。

我的小黑客就是保持jobject * self在每一个C++侧GameObject对象,而真正的Java端GameObject(而不是说只是包装指针之一)施工过程中指定真正的Java对象的地址。这样,我可以在C++端GameObject中定义一个多态的getSelf方法,并在java中愉快地使用结果。有没有办法将必要的代码注入swig生成的文件?

感谢

注意:如果你试图在Android董事,他们都没有奏效,这是因为当前稳定版本不支持它。从swig网站下载Bleeding Edge。但是我在2012年3月22日写这篇文章,这个说明很快就没有必要了。析构函数不是虚拟的原因在于Bleeding Edge版本使得程序在析构函数中崩溃,并且使其非虚拟化现在似乎可以控制它。

+0

所以你的问题的简短版本是你想能够(例如)在Java中派生'GameObject'并且仍然能够在Java派生类型被传递给'collidedWith'的Java实现时在Java中进行投射?很确定你的小黑客可以包装在类型图中,如果是这样的话。 – Flexo 2012-03-23 18:22:31

+0

准确!我认为swig会有注入代码的方式,但我对swig很陌生。我会检查typemaps。 – enobayram 2012-03-23 18:47:22

+0

那么我会在明天写一个答案。 – Flexo 2012-03-23 18:50:49

回答

8

我已经把这个问题的解决方案放在一起。这不是你在你的问题中提出的解决方案,它是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实际上使问题明显。

我使用的技巧是在创建时自动将GameObject的所有Java派生实例注册到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 typemap)。

+0

哇!这么多的努力和一个很好的答复。这似乎解决了我的问题,并且教会了我很多关于swig的知识。看起来我们有时需要覆盖一半的swig才能获得我们想要的。非常感谢。 – enobayram 2012-03-27 22:11:00

+0

关于SWIG的好处是,它可以让你在你想要的时候做这样的事情,但是封装它,这样你只需要工作在“奇怪”的位上,所有的咕噜声都可以为你完成。 – Flexo 2012-03-28 09:10:25

+0

软件工程师永无止境的决策:“我应该在哪里投入我的时间?”。 SWIG似乎是一个很好的交易,因为它对跨语言问题的处理看起来像是一般。我的意思是,作为外部代码生成器,它没有大多数限制,纯粹基于库的解决方案(如boost python)受到影响。 – enobayram 2012-03-28 09:21:58