Search code examples
javac++java-native-interface

calling java method based on jobject fails (invalid reference?)


I have the following problem:

in my java program I'm calling a native function that creates an object of the "MyEventReceiver" class in C++, later in that java program I'm calling an native function that calls the "test" method of that object. In this "test"-method I want to call a method of the java object that I used to call the second native function, but as soon as make this call, the jvm crashes.

This is the native code that creates the MyEventReceiver object:

JNIEXPORT jlong JNICALL Java_irr4jEventReceiver_native_1createEventReceiver
  (JNIEnv *env, jobject obj){
    MyEventReceiver *rec = new MyEventReceiver(env, obj);
    return (long)rec;
}

this is the native code that i use later in the program to call the "test" method in that object:

JNIEXPORT void JNICALL Java_irr4jEventReceiver_native_1testmethode
  (JNIEnv *env, jobject obj, jlong ptrrec){
  MyEventReceiver *rec = (MyEventReceiver*)ptrrec;
  rec->test();
}

and this is the MyEventReceiver class:

class MyEventReceiver : public IEventReceiver
{
public:
JNIEnv *myenv;
jobject receiverobj;
jclass SEventclass;
jobject eventobj;
jmethodID cid;

jclass cevrec;
jmethodID meth2;
public:    

    void test(){

         eventobj = myenv->AllocObject(SEventclass);                            
         eventobj = myenv->NewObject(SEventclass, cid);                       
         myenv->CallVoidMethod(receiverobj,meth2,eventobj); //this is the line that causes the crash

    }


    MyEventReceiver(JNIEnv *env, jobject obj)
    {

        this->myenv=env;    
        receiverobj = env->NewGlobalRef(obj);

        SEventclass = myenv->FindClass("SEvent");
        cid = myenv->GetMethodID(SEventclass,"<init>", "()V");   
        cevrec = myenv->FindClass("MyEventReceiver");                       
        meth2 =myenv->GetMethodID(cevrec, "OnEvent", "(LSEvent;)V");                     
        //test();
    }
};

if I call the method "test" at the end of the constructor, it works... only if I make the call later out of the java program... I think it has something to do with the jobject "receiverobj"... seems to get invalid after some time but I don't know why ...I stripped the code a bit...deleted some debug code. The "eventobj" I'm creating is fine... I can call other methods of that object, the methodIDs are also fine, just the line:

    myenv->CallVoidMethod(receiverobj,meth2,eventobj);

gives me problems and i dont know why :)

crash message is:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x67d594f4, pid=5292, tid=2400
#
# JRE version: 7.0_21-b11
# Java VM: Java HotSpot(TM) Client VM (23.21-b01 mixed mode, sharing windows-x86 )
# Problematic frame:
# V  [jvm.dll+0xa94f4]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# G:\irr4jjava\irr4j\hs_err_pid5292.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
#

EDIT:

Thanks for the help so far. I tried to use global refs and attached thread but I can't get it to work. I'm not sure if I understood the thing with the attached thread...

I modified the first native method to create a global reference to the jobject and pass it to the constructor: JavaVM *jvm;

JNIEXPORT jlong JNICALL Java_irr4jEventReceiver_native_1createEventReceiver
  (JNIEnv *env, jobject obj){
      jobject recobj = env->NewGlobalRef(obj);
      env->GetJavaVM(&jvm);
      MyEventReceiver *rec = new MyEventReceiver(recobj);
      return (long)rec;
}   

in the class "MyReceiverObject" I try attach the thread but it still crashes:

class MyEventReceiver : public IEventReceiver
{
public:
JNIEnv *myenv;
jobject receiverobj;
jclass SEventclass;
jobject eventobj;
jmethodID cid;
jclass cevrec;
jmethodID meth2;
public:

    void test(){

         jvm->AttachCurrentThread((void**)&myenv,NULL);

         eventobj = myenv->AllocObject(SEventclass);     //crash                        
                 eventobj = myenv->NewObject(SEventclass, cid);                      
         myenv->CallVoidMethod(receiverobj,meth2,eventobj);
    }


    MyEventReceiver(jobject obj)
    {

        jvm->AttachCurrentThread((void**)&myenv,NULL);      
        receiverobj=obj;

        SEventclass = myenv->FindClass("SEvent");
        cid = myenv->GetMethodID(SEventclass,"<init>", "()V");      
        cevrec = myenv->FindClass("MyEventReceiver");                       
        meth2 =myenv->GetMethodID(cevrec, "OnEvent", "(LSEvent;)V");        

        //test();
    }
};

Solution

  • You need to read the chapter in the JNI Specification about global references. The jobject you were passed in your JNI method is only valid for the life of that method. You need to convert it to a GlobalRef, maybe a WeakGlobalRef, before you store it anywhere that is going to outlive the current invocation.

    A similar thing applies to the JNIEnv pointer. It's only valid for the duration of the method it is passed to. If you want to use one in a different thread or at a later time you must get a new one via AttachThread().