12

I got a couple of JNI functions that have to work on the same c++ object. I would like to save that object in the java object through which the JNI functions are invoked, but it seems that Java has no way to store pointers for functions to access later.

Okay I realize that I'm doing a terrible job explaining myself so here is an example:

void clear_numbers(JNIEnv *env, jobject me) {
   me.myCppVector.clear();
}

void set_number(JNIEnv *env, jobject me, jint index, jint num) {
   me.myCppVector[index]=num;
}

jint get_number(JNIEnv *env, jobject me, jint index) {
   returnme.myCppVector[index];
}

My problem is creating a jobject.myCppVector so that I'll be able to use it from different function calls.

I hope someone understands my ramblings

6
  • 1
    maybe convert the pointer to an integer, and then store the integer into a field of the Java object
    – ignis
    Commented Jun 7, 2011 at 19:08
  • Isn't that considered to be unsafe? Also, is sizeof(jint) equal to sizeof(void*) on all platforms?
    – Afiefh
    Commented Jun 7, 2011 at 19:22
  • I don't know of any reasons for which it should be unsafe. However, pointers have different sizes on different platforms, while Java primitive types do not, and of course this means you would need to map pointers to primitives of different types in the Java code - int for 32 bit, long for 64 bit, etc. java.sun.com/docs/books/jls/second_edition/html/…
    – ignis
    Commented Jun 7, 2011 at 19:33
  • 3
    An alternative would be defining a "global" std::map<jobject, void*> in the native code, so that you would store pointers therein, and never pass them to Java code (which would not be able to use them for anything in particular, anyway). PS: yes, I think that's definitely better than my previous advice
    – ignis
    Commented Jun 7, 2011 at 19:36
  • 1
    Well, then you would have one map for each class
    – ignis
    Commented Jun 7, 2011 at 19:41

1 Answer 1

9

I think the standard way of going about it is to use a long in java to store the pointer, that way your java code will work on both 32bit and 64bit systems. Obviously you need to compile a different C++ .so/.dll for each platform.

So a simple java wrapper for a C++ class with one method will look something like:

class JavaClass
{
    private long native_ptr = 0;
    private native long createNativeInstance( params );
    private native String nativeMethod( params );
    private native void destroyNativeInstance( long p_native_ptr );
    public JavaClass( params )
    {
        this.native_ptr = createNativeInstance( params );
    }
    public String javaMethod( params )
    {
        nativeMethod( this.native_ptr, params );
    }
    public void finalize()
    {
        destroyNativeInstance( this.native_ptr );
    }
}

And your C++ wrapper code looks something like this. (I have named the C++ class CppClass)

JNIEXPORT jlong JNICALL Java_JavaClass_createNativeInstance
    ( JNIEnv*   p_jenv
    , jobject   p_jthis
    , params )
{
    // Convert params from Java types to C++ types if required
    return (jlong) new CppClass( converted_params );
}

JNIEXPORT void JNICALL Java_JavaClass_destroyNativeInstance
    ( JNIEnv*   p_jenv
    , jobject   p_jthis
    , jlong     p_native_ptr )
{
    if( p_native_ptr )
        delete (CppClass*)p_native_ptr;
}

JNIEXPORT jstring JNICALL Java_JavaClass_nativeMethod
    ( JNIEnv*   p_jenv
    , jobject   p_jthis
    , jlong     p_native_ptr
    , params
    )
{
    // Convert params from Java types to C++ types if required
    std::string cpp_result = ((CppClass*)p_native_ptr)->cppMethod( converted_params );
    jstring java_result = p_jenv->NewStringUTF( cppResult.c_str() );
    return java_result;
    // NOTE: std::string destructor will free cpp_result memory
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.