Search code examples
androidc++java-native-interface

JNI keeping a global reference to an object, accessing it with other JNI methods. Keeping a C++ object alive across multiple JNI calls


I am just starting out with JNI and I have a following problem.

I have a C++ library that has a simple class. I have three JNI methods called from the Java Android project that, instatiate said class, call a method on the instantiated class, and destroy it, respectively. I keep a global reference to this object, so it would be available for me in the other two JNI methods.

I suspect that I cannot do this. When I run the app, I get a runtime error (used reference stale), and I suspect that this is because the global refernce is invalid at subsequent calls to other JNI methods.

Is the only way to achieve what I want (have the object live across multiple JNI calls), to actually pass back the pointer to the instantiated class back to Java, keep it around there, and then pass it back to the JNI functions? If so, that's fine, I want to make sure I can't do it with a global reference, and I'm not just missing something.

I have read the documentation and the chapters about global/local references in JNI, but it seems like that only applies to Java classes, and not my own, native C++ classes, or am I wrong.

Here is the code if my description is not clear (summarizing, I am wondering if this mechanism of persisting objects will work at all):

Java:

package com.test.ndktest;

import android.app.Activity;
import android.os.Bundle;
import android.app.AlertDialog;

public class NDKTestActivity extends Activity {
static {
    System.loadLibrary("ndkDTP");
}

private native void initializeTestClass();
private native void destroyTestClass(); 

private native String invokeNativeFunction();


@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    initializeTestClass();

    String hello = invokeNativeFunction();

    destroyTestClass();

    new AlertDialog.Builder(this).setMessage(hello).show();
}

}

JNI header:

extern "C" {

jstring Java_com_test_ndktest_NDKTestActivity_initializeTestClass(JNIEnv* env,     jobject javaThis);
jstring Java_com_test_ndktest_NDKTestActivity_destroyTestClass(JNIEnv* env, jobject javaThis);
jstring Java_com_test_ndktest_NDKTestActivity_invokeNativeFunction(JNIEnv* env, jobject javaThis);

};

JNI body:

#include <string.h>
#include <jni.h>
#include <ndkDTP.h> //JNI header
#include <TestClass.h> //C++ header

TestClass *m_globalTestClass;

void Java_com_test_ndktest_NDKTestActivity_initializeTestClass(JNIEnv* env, jobject javaThis) {

m_globalTestClass = new TestClass(env);
}

void Java_com_test_ndktest_NDKTestActivity_destroyTestClass(JNIEnv* env, jobject    javaThis) {

delete m_globalTestClass;
m_globalTestClass = NULL;
}


jstring Java_com_test_ndktest_NDKTestActivity_invokeNativeFunction(JNIEnv* env, jobject javaThis) {

jstring testJS = m_globalTestClass->getString();

return testJS;

}

C++ header:

class TestClass
{
 public:
 jstring m_testString;
 JNIEnv *m_env;

 TestClass(JNIEnv *env);

 jstring getString();
};

C++ body:

#include <jni.h>
#include <string.h>

#include <TestClass.h>

TestClass::TestClass(JNIEnv *env){
  m_env = env;

  m_testString =    m_env->NewStringUTF("TestClass: Test string!");
}

jstring TestClass::getString(){
 return m_testString;
}

Thanks


Solution

  • The problem with your implementation is the jstring data member. NewStringUTF() creates a Java String object to return from a JNI method. So it is a Java local reference. However, you are storing this inside a C++ object and try to use it across JNI calls.

    You should keep a better distinction between C++ objects, Java and the JNI interface in between. In other words, the C++ should use a C++ way of storing strings (like std::string). The JNI implementation of InvokeNativeFunction() should convert that to a jstring as return value.

    PS: There are cases that require the C++ implementation to keep references to Java objects (or the other way around). But it makes the code more complex and prone to memory bugs if not done right. So you should only use it where it really adds value.