1

I'm attempting to create a C++ callback that gets called if a certain event occurs. That callback is supposed to call a Java method if that is the case.

In order to be able to call that Java method I need access to the JNIEnv object which is why I set everything up in a JNI function which gets called from Java.

Setting things up is straight forward. I obtain the jclass, the jmethodID, etc. within the JNI function. The code looks as follows:

JNIEXPORT void JNICALL Java_my_customclass_register(JNIEnv* jenv, jobject obj, jlong nativePeerAddress, jstring callbackName)
{
  jclass customClass       = jenv->FindClass("<fully qualified class name>");
  const char* methodName   = jenv->GetStringUTFChars(callbackName, JNI_FALSE);
  jmethodID callbackMethod = jenv->GetMethodID(customClass, methodName, "()V");
  jenv->ReleaseStringUTFChars(callbackName, methodName);

  reinterpret_cast<CustomClass*>(nativePeerAddress)->SetCallback([jenv, obj, callbackMethod]()
  {
    jenv->CallVoidMethod(obj, callbackMethod);
  });
}

The line with the SetCallback call is where the C++ lambda that acts as callback gets set. The callback itself eventually, once the event occurs, calls the Java method using the line

jenv->CallVoidMethod(obj, callbackMethod);

The problem I'm facing is that this call happens long after the above listed JNI method terminated. If the callback gets invoked I eventually get the following error message:

JNI DETECTED ERROR IN APPLICATION: use of invalid jobject

That call succeeds as expected if I move that line of code out of the C++ callback and place it right below the line

jenv->ReleaseStringUTFChars(callbackName, methodName);

However, it does not work when executed from within the C++ callback. I think the problem here is that the jobject obj is longer a valid object. My assumption is that this is the case because the JNI method already terminated and disposed the jobject (the C++ wrapper, not the Java object).

However, it is unclear to me how I can obtain a valid jobject on which I can execute the jmethodID. obj would point to the right Java object but it is just not working that way.

Any ideas how I can get a valid jobject?

ackh
  • 1,648
  • 2
  • 18
  • 36
  • 2
    You need to create a `GlobalRef`. The `obj` Passed into your JNI method is only valid for the duration of that method. You also need to add proper error-checking to every JNI call you make. – user207421 Feb 13 '20 at 23:11
  • 3
    You also can't use that `JNIEnv*` in a different thread. You have to call `Attach/DetachCurrentThread()` to get your own. You clearly need to read the JNI Specification. It's all in there. – user207421 Feb 14 '20 at 00:33
  • `GlobalRef` is exactly what I was looking for, thanks for the keyword. If you post your comment as answer I'll accept it. Regarding error checking: Sure, I'm aware of the dangers of not checking whether something goes wrong or not. I've just omitted it because it does not help clarifying my Problem. Regarding the `JNIEnv*`: Yes, I'm well aware that I can only use that object from within the same thread. I already make sure that my callback gets executed on the same thread. This can be done on Android using `ALooper` and a pipe. – ackh Feb 14 '20 at 08:50
  • 1
    @ackh How and when do you plan on cleaning up those global references? [Java has only a limited number of global references available](https://stackoverflow.com/questions/45384947/why-does-jni-global-reference-have-maximum-number-limit). – Andrew Henle Feb 14 '20 at 14:06
  • @AndrewHenle `Java_my_customclass_register` gets called once on the Java object to register the callback. Within that JNI function I create a new C++ object that allocates the global reference in its constructor and releases it in its destructor. The callback then uses that object to invoke the Java method. So, hiting any JNI restriction shouldn't be any issue whatsoever. But thanks for bringing it up, its an interesting reference. – ackh Feb 14 '20 at 17:29

0 Answers0