1

I'm trying to call a Java function from a C++ class using JNI on Android. I have searched and searched but haven't found my exact case. I can call methods in my c++ library from Java, but am having issues doing the reverse. I've messed with it for two days now and am wasting time, so could someone more knowledgable than I help me out?

Full goal: Preserve the JNIEnv OR just the JavaVM (to get and attach a valid JNIEnv later) passed to a native c++ JNI EXPORT call from Java for later use by a c++ class method (not a JNI EXPORT).

So, Java class method calls native c++ method, passing its JNIEnv* and jobject. Store those as static class members in a c++ class. Later, a method of that c++ class uses those static members to callback a Java method of the same class that originally passed its context or whatever.

I've tried using env->NewGlobalRef(someObj); but it's strange because that will make some future uses of the reference objects succeed, but some still fail.

Here's some code:

Java code:

//this is what I want to call from native codepublic void something(String msg){//do something with msg}public void somethingElse(){    callNative();}private native void callNatve();//access nativestatic{    System.loadLibrary("someLib");}

All of the above works fine, the c++ trying to do the same however, does not. (Note: I need the class in my native library as a class and not standalone static calls)

C++ code:(Note: for simplicity here everything is public)

MyClass.h:

#include <string>#include <jni.h>class MyClass{    //ctor    //dtor    void someCall(std::string)    static JNIEnv* envRef;    static JavaVM* jvmRef;    static jobject objRef;};

//////////////////////////////////////////////////////////////////////////////////////MyClass.cpp

#include <MyClass.h>//static membersMyClass:;:JNIEnv* envRef;MyClass::JavaVM* jvmRef;MyClass::jobject objRef;//this is the method whose instructions are crashingvoid MyClass::someCall(std::string msg){     //works assuming i call env->NewGlobalRef(MyClass::objRef) or setup/reattach from jvm in exported call or here     jstring passMsg = envRef->NewStringUTF(msg.c_str());    clsRef = envRef->GetObjectClass(objRef);    if(clsRef == NULL)    {        return;    }    //This doesn't cause crash, but if I call FindClass("where/is/MyClass"); it does... strange    jmethodID id = envRef->GetMethodID(clsRef, "something", "(Ljava/lang/String;)V");    if(id == NULL)    {        return;    }    //Crashes    //envRef->CallVoidMethod(clsRef, id, passMsg);    if(envRef->ExceptionCheck())    {        envRef->ExceptionDescribe();    }    //Also crashes    //jvmRef->DetachCurrentThread();}//this worksextern "C"{    JNIEXPORT void JNICALL Java_com_my_project_class_callNative(JNIEnv* env, jobject obj)    {        MyClass::objRef = env->NewGlobalRef(obj);        MyClass::envRef = env;        //tried both        //MyClass::envRef->GetJavaVM(&MyClass::jvmRef);        env->GetJavaVM(&MyClass::jvmRef);        //Tried this        /*        int envStat = MyClass::jvmRef->GetEnv((void**)&MyClass::envRef, JNI_VERSION_1_6);        if(envStat == JNI_EDETACHED)        {            //TODO: LOG            //std::cout << "GetEnv: not attached" << std::endl;            if(MyClass::jvmRef->AttachCurrentThread(&MyClass::envRef, NULL) != 0)            {                //TODO: LOG                //std::cout << "Failed to attach" << std::endl;            }        }else if(envStat == JNI_OK)        {            //        }else if(envStat == JNI_EVERSION)        {            //TODO: LOG            //std::cout << "GetEnv: version not supported" << std::endl;        }        */        //calling detachcurrentthread here crashes if set above        MyClassObj.someCall(an std::string);    }}

I've tried a few different approaches but they all cause crashes. I do DeleteGlobalRef() too when I use it, but it crashes way before then. Any insight is appreciated

EDIT #1:As per Michael's suggestion, I've implemented the JNI_OnLoad function and have cached just the JavaVM* from there. Inside the MyClass::someCall(std::string) method I then use the JavaVM to get the JNIEnv, initialize a jclass object using env->FindClass and get the methodID for the something(String) java method, but attempting to callback to Java with CallVoidMethod results in a crash still.

OnLoad defined as extern "C" in MyClass.cpp:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved){    MyClass::jvmRef = jvm;    return JNI_VERSION_1_6;}

Updated MyClass::someCall definition:

void MyClass::someCall(std::string msg){    //Get environment from cached jvm    JNIEnv* env;    jclass cls;    int envStat = MyClass::jvmRef->GetEnv((void**)&env, JNI_VERSION_1_6);    bool attached = false;    if(envStat == JNI_EDETACHED)    {        //TODO: LOG        if(JavaInterface::jvmRef->AttachCurrentThread(&env, NULL) != 0)        {            //TODO: LOG            // "Failed to attach"            return;        }else if(envStat == JNI_OK)        {            attached = true;        }else if(envStat == JNI_EVERSION)        {            //TODO: LOG            // "GetEnv: version not supported"        }    }     cls = env->FindClass("package/location/project/JavaClass");    if(cls == NULL)    {        //TODO: LOG        return;    }    jmethodID id = env->GetMethodID(cls, "something", "(Ljava/lang/String;)V");    if(id == NULL)    {        return;    }    jstring passMsg = env->NewStringUTF(msg.c_str());    //Crashes    env->CallVoidMethod(cls, id, passMsg);    if(attached)        jvmRef->DetachCurrentThread(); }
askedOct 8, 2017 at 0:55
ErnieB's user avatar
4
  • 1
    You're not supposed to cacheJNIEnv pointers. TheJavaVM pointer is safe to cache, so you can do that e.g. inJNI_OnLoad. And then you use thatJavaVM* to get aJNIEnv* usingGetEnv/AttachCurrentThread.CommentedOct 9, 2017 at 5:32
  • Note that when you obtain theJNIEnv*, you need to keep track of whether or not the thread already was attached to the VM. Because you mustn't callDetachCurrentThread unless you previously calledAttachCurrentThread on that thread while it was deatched.CommentedOct 9, 2017 at 7:14
  • Ok, I've implemented a super simple JNI_OnLoad() and cache the jvm in it. I can get the JNI_Env from that while inside MyClass::someCall(std::string msg), find the jclass fine with FindClass, and get the methodid, but CallVoidMethod still crashes.CommentedOct 9, 2017 at 21:33
  • In your updatedsomeCall function you're doingenv->FindClass before you've made sure that you actually have a validJNIEnv*.CommentedOct 10, 2017 at 5:38

1 Answer1

0

Okay, the error after the Edit #1 changes is that I was passing the wrong object to CallVoidMethod(). What I ended up doing, which works, is storing the jobject passed by callNative(JNIEnv* env, jobject obj) as a static member of MyClass and passing it to CallVoidMethod instead of cls.

An earlier call:

JNIEXPORT void JNICALL Java_path_to_project_JavaClass_nativeCall(JNIEnv* env, jobject obj){    JavaInterface::objRef = env->NewGlobalRef(obj)}

DeleteGlobalRef(objRef) is called elsewhere after it's no longer needed. Then, the only change was in the native "someCall" method:

void MyClass::someCall(std::string msg){    //everything here is the same    //Now either of these will work    cls = env->FindClass("com/empsoftworks/andr3ds/NativeInterface");//cls = env->GetObjectClass(objRef);    //unchanged stuff    //This is what fixed it    env->CallVoidMethod(objRef, id, passMsg);}
answeredOct 9, 2017 at 23:34
ErnieB's user avatar
Sign up to request clarification or add additional context in comments.

3 Comments

That won't work either in the long run. You can't storejobjects acoss JNI method invocations. You need aGlobalRef. You don't seem to have done much reading. I suggest you study the JNI reference. All of it.
That's what I said. You need to use aGlobalRef, otherwise it won't work in the long run.
Method IDs are safe to cache, so there's no point in looking up the method ID more than once (e.g. inJNI_OnLoad).

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.