SWIG Java Retaining Class information of the objects bouncing from C++

后端 未结 1 1254
隐瞒了意图╮
隐瞒了意图╮ 2020-12-03 22:10

Ok, there\'s a keyword that I\'ve intentionally kept away from the tags and the title. That\'s \"Android\", but that\'s because even though the project is in Android, I don\

相关标签:
1条回答
  • 2020-12-03 22:43

    I've put together a solution to this problem. It's not quite the solution you suggested in your question though, it's more code on the Java side and no extra on the JNI/C++ side. (I found doing it the way you suggested quite tricky to get correct in all the possible cases).

    I simplified your classes down to a single header file:

    class GameObject {
    };
    
    class CollisionListener {
    public:
        virtual bool collidedWith(GameObject &) { return false; }
        virtual ~CollisionListener() {} 
    };
    
    inline void makeCall(GameObject& o, CollisionListener& c) {
        c.collidedWith(o);
    }
    

    which also added makeCall to actually make the problem obvious.

    The trick I used is to register all Java derived instances of GameObject in a HashMap automatically at creation time. Then when dispatching the director call it's just a question of looking it up in the HashMap.

    Then the module file:

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

    Note that since all Java instances are being stored in a HashMap we need to use a WeakReference to be sure that we aren't prolonging their lives and preventing garbage collection from happening. If you care about threads then add synchronisation as appropriate.

    I tested this with:

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

    Where JCollisionListener is:

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

    and JGameObject is:

    public class JGameObject extends GameObject {
    }
    

    (For reference if you wanted to do the other approach you would be looking at writing a directorin typemap).

    0 讨论(0)
提交回复
热议问题