Search code examples
androidc++java-native-interface

Invalid JNI reference (calling Java method from c++)


I'm having a problem with a call to enable the GPS localization from c++. I generated the JNI wrappers with SWIG. The logcat says :

01-02 17:14:03.816: D/dalvikvm(6165): Trying to load lib /data/data/com.example.swig/lib/libcppinterface.so 0x44e7ebf0
01-02 17:14:03.976: D/dalvikvm(6165): Added shared lib /data/data/com.example.swig/lib/libcppinterface.so 0x44e7ebf0
01-02 17:14:04.886: W/dalvikvm(6165): JNI WARNING: 0x44e8bb48 is not a valid JNI reference
01-02 17:14:04.886: W/dalvikvm(6165):              in Lcom/example/swig/cpplib/cppinterfaceJNI;.LocationSpotter_refresh (JLcom/example/swig/cpplib/LocationSpotter;)V (CallVoidMethodV)
01-02 17:14:04.886: I/dalvikvm(6165): "main" prio=5 tid=1 RUNNABLE
01-02 17:14:04.886: I/dalvikvm(6165):   | group="main" sCount=0 dsCount=0 s=N obj=0x4001d8e0 self=0xccb0
01-02 17:14:04.886: I/dalvikvm(6165):   | sysTid=6165 nice=0 sched=0/0 cgrp=default handle=-1345026008
01-02 17:14:04.886: I/dalvikvm(6165):   | schedstat=( 226108400 549900071 66 )
01-02 17:14:04.886: I/dalvikvm(6165):   at com.example.swig.cpplib.cppinterfaceJNI.LocationSpotter_refresh(Native Method)
01-02 17:14:04.896: I/dalvikvm(6165):   at com.example.swig.cpplib.LocationSpotter.refresh(LocationSpotter.java:56)
01-02 17:14:04.896: I/dalvikvm(6165):   at com.example.swig.MainActivity.onCreate(MainActivity.java:50)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.ActivityThread.access$2300(ActivityThread.java:125)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.os.Handler.dispatchMessage(Handler.java:99)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.os.Looper.loop(Looper.java:123)
01-02 17:14:04.896: I/dalvikvm(6165):   at android.app.ActivityThread.main(ActivityThread.java:4627)
01-02 17:14:04.896: I/dalvikvm(6165):   at java.lang.reflect.Method.invokeNative(Native Method)
01-02 17:14:04.896: I/dalvikvm(6165):   at java.lang.reflect.Method.invoke(Method.java:521)
01-02 17:14:04.896: I/dalvikvm(6165):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
01-02 17:14:04.896: I/dalvikvm(6165):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
01-02 17:14:04.896: I/dalvikvm(6165):   at dalvik.system.NativeStart.main(Native Method)
01-02 17:14:04.906: E/dalvikvm(6165): VM aborting

I trigger the call from Java with this line :

LocationSpotter.getLocationSpotter().refresh();

getLocationSpotter() triggers in c++ this code which creates a jobject (jSpotter) :

// Retrieve the current JNIEnv* with the cached JVM
JNIEnv* env;
AndroidLocationSpotter::cachedJVM->AttachCurrentThread(&env, NULL);

jclass clazz = env->FindClass("com/example/swig/AndroidLocationSpotter");
jmethodID construct = env->GetMethodID(clazz, "<init>", "()V");

jSpotter = env->NewObject(clazz, construct);

and after the method refresh() triggers this code which makes the application crash :

// Retrieve the current JNIEnv* with the cached JVM
JNIEnv* env;
AndroidLocationSpotter::cachedJVM->AttachCurrentThread(&env, NULL);

jclass clazz = env->FindClass("com/example/swig/AndroidLocationSpotter");
jmethodID refresh = env->GetMethodID(clazz, "refresh", "()V");

env->CallVoidMethod(jSpotter, refresh); // The line where the crash happens

I also need to mention that I initiate "cachedJVM" in the JNI_OnLoad function. Hope someone could help me on this, thanks

EDIT

Working code :

// Retrieve the current JNIEnv* with the cached JVM
JNIEnv* env;
AndroidLocationSpotter::cachedJVM->AttachCurrentThread(&env, NULL);

jclass clazz = env->FindClass("com/example/swig/AndroidLocationSpotter");
jmethodID construct = env->GetMethodID(clazz, "<init>", "()V");

jSpotter = env->NewObject(clazz, construct);

jSpotter = env->NewGlobalRef(jSpotter);

Solution

  • You need to create a new global reference using the returned jSpotter reference. The reference you are currently using is local to your current frame, and will go out of scope (probably when you DetachCurrentThread()).

    You need to call env->NewGlobalRef(jSpotter) or env->NewWeakGlobalRef(jSpotter) (both of which have corresponding free functions) and use the reference returned.

    EDIT

    Here's an example of proper attach/detach handling (assuming you don't need the thread for further Java operations):

      JNIEnv* env;
      JavaVM* jvm = AndroidLocationSpotter::cachedJVM;
    
      int attached = jvm->GetEnv((void *)&env, JNI_VERSION_1_4) == JNI_OK;
      if (!attached) {
        if (jvm->AttachCurrentThread((void *)&env, NULL) != JNI_OK) {
          // Attach failed, handle error condition
          return;
        }
      }
    
      // Pushing a frame ensures proper cleanup of any local references you might generate
      // in your JNI code, e.g. with NewLocalRef (optional)                                                                                                                                                                                
      if (env->PushLocalFrame(16) < 0) {
        // Handle failed frame push error
      }
      else {
        // example local reference generation: ref2 = env->NewLocalRef(ref);
    
        //
        // Perform your primary Java operations here
        //
    
        env->PopLocalFrame(NULL);
      }
    
      if (!attached) {
        jvm->DetachCurrentThread();
      }