Search code examples
javaandroid-ndkjava-native-interfacefieldinstance-variables

JNI - how to use multiple Jni wrapper instances with different fields?


background

I have an android project that uses JNI (using NDK) to code in both Java and C/C++.

I've created a Jni java wrapper on the java side that will do all the Jni oprerations by itself, while no other java class can access the jni operations directly other than this wrapper.

the problem

problem is , i wish to create multiple instances of this wrapper, while the Jni part should have an instance per Jni wrapper.

this is a problem, since the Jni part holds the same fields for all instances.

the question

How can i solve this problem, so that for each java instance of the jni wrapper, there will be an instance on the jni part?

I was thinking, maybe I could put all of the fields into a C++ class, and have an init() function that will return a new instance of it for the CTOR of the JniWrapper, and from then, for each JNI function that needs fields, it will get this class as a parameter . maybe it could be a pointer as shown on this link.

sadly, i have no idea how to do it.

can anyone please help?

sample

here's a sample code that I hope will make things clearer for those who didn't understand the problem:

java part:

public class JniWrapper
  {
  static
    {
    System.loadLibrary("JniTest");
    }

  private native void foo(Bitmap bitmap);
  }

jni part:

...
// sadly, all of those fields are actually a global fields
int _intField;
float _floatField;    
//those are just sample fields. i would also like to store pointers and objects...

JNIEXPORT void JNICALL ...foo(JNIEnv * env, jobject obj, jobject bitmap)
  {
  // do something with the fields, as if they all belong to the JniWrapper, 
  // and no other instances of JniWrapper are allowed to change them
  }

Solution

  • I've found a possible solution (link here), to either use a jlong or jobject to be a handle (or pointer, if you wish) to the object that was created on the JNI side.

    people said it's better to use a jobject as ByteBuffer instead of jlong for better compatibility.

    the solution is :

    Java side:

    private native ByteBuffer init();
    private native void foo(ByteBuffer handle);
    

    JNI side:

    /**a class to hold the fields*/
    class FieldsHolder
      {
      ... //private fields, for each instance
      }
    

    creating the JNI object and sending to java side:

    JNIEXPORT jobject JNICALL ...init(JNIEnv * env, jobject obj)
      {
      FieldsHolder* myClass= new FieldsHolder();
      ... //prepare fields of the class
      return env->NewDirectByteBuffer(myClass, 0);
      }
    

    re-using the JNI object :

    JNIEXPORT void JNICALL ...foo(JNIEnv * env, jobject obj, jobject handle)
      {
      FieldsHolder* myClass= (FieldsHolder*) env->GetDirectBufferAddress(handle);
      //now we can access the fields again.
      }