6

I'm writing an OpenGL C/C++ application which i'm porting to Android through Android NDK, JNI support. I'm having difficulties executing code from JAVA callback signaled from native.

Here is the code:

class OpenGLSurfaceView extends GLSurfaceView 
{
…
     public OpenGLSurfaceView(Context context, int deviceWidth, int deviceHeight) 
    {
        super(context);
        nativeObj = new NativeLib();
        mRenderer = new OpenGLRenderer(context, nativeObj, deviceWidth, deviceHeight);
        setRenderer(mRenderer);
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }
…
    private void CallBack()
    {
        // force redraw
        requestRender();
    }
}


class OpenGLRenderer implements GLSurfaceView.Renderer 
{
    …
public void onSurfaceCreated(GL10 gl, EGLConfig config) 
    {
        nativeObj.init(…);
        nativeObj.cachejavaobject(JNIEnv *env, jobject obj); // for caching obj on native side
    }

    public void onSurfaceChanged(GL10 gl, int w, int h) 
    {
    }

    public void onDrawFrame(GL10 gl) 
    {
        nativeObj.draw(…);
    }
}

And in native code i have a function texturesLoaded() that is signaled when some textures are loaded completely on another thread and i need to force a refresh from nativeLib.draw(…) on the JAVA side. Here is how i do it :

I cache the JavaVM, jClass, jMethodID on JNI_OnLoad, and gJObjectCached

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
gJVM = jvm;  // cache the JavaVM pointer

LogNativeToAndroidExt("JNILOAD!!!!!!!!!!");
JNIEnv *env;
int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
if(status < 0)
{
    LogNativeToAndroidExt("Failed to get JNI environment, assuming native thread");
    status = gJVM->AttachCurrentThread(&env, NULL);
    if(status < 0)
    {
        LogNativeToAndroidExt("callback_handler: failed to attach current thread");
        return JNI_ERR;
    }
}

gJClass = env->FindClass("com/android/newlineactivity/NewLineGLSurfaceView");
if(gJClass == NULL)
{
    LogNativeToAndroidExt("Can't find Java class.");
    return JNI_ERR;
}

gJMethodID = env->GetMethodID(gJClass, "callback", "()V");
if(gJMethodID == NULL)
{
    LogNativeToAndroidExt("Can't find Java method void callback()");
    return JNI_ERR;
}

return JNI_VERSION_1_6;

}

JNIEXPORT void Java_com_android_OpenGL_NativeLib_cachejavaobject(JNIEnv* env, jobject obj)
{
    // cache the java object
    gJObjectCached = obj;
...
}

and then on texturesLoaded() i do :

void texturesLoaded()
{
    // Cannot share a JNIEnv between threads. You should share the JavaVM, and use JavaVM->GetEnv to discover the thread's JNIEnv
    JNIEnv *env = NULL;
    int status = gJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
    if(status < 0)
    {
        LogNativeToAndroidExt("callback_handler: failed to get JNI environment, assuming native thread");
        status = gJVM->AttachCurrentThread(&env, NULL);
        if(status < 0)
        {
            LogNativeToAndroidExt("callback_handler: failed to attach current thread");
            return;
        }
    }

    LogNativeToAndroidExt("Calling JAVA method from NATIVE C/C++");
    env->CallVoidMethod(gJObjectCached, gJMethodID);
    LogNativeToAndroidExt("DONE!!!");
}

Result… from native side i get the class, i get the method, method gets called, but when it reaches/calls requestRender() inside(or trying to access any other member method of GLSurfaceView it crashes!)

I cannot try with CallStaticVoidMethod(gJClass, gjMethodID); because then i don't have access to requestRender();

Any ideas or opinions, maybe i'm doing something wrong here.

Thanks

eKKo82
  • 61
  • 1
  • 1
  • 4
  • `requestRender()` is in Java, so crashes usually are accompanied by an exception. Could you mention what that exception is. The Android ADB logs should reveal such information. Go [Here](http://developer.android.com/guide/developing/debugging/debugging-log.html) to find out about logs – Warpspace Sep 01 '11 at 10:36

1 Answers1

3

You need to create global references to the class/object that you stash away. The references you're saving are local references, which can't be shared across threads and disappear when the runtime cleans up the JNI local reference stack.

Check out the Sun/Oracle documentation for global and local references, and check out JNI methods JNIEnv::NewGlobalRef and JNIEnv::DeleteGlobalRef.

gJClass = env->NewGlobalRef(env->FindClass( ... ));

gJObjectCached = env->NewGlobalRef(obj);

(Edit: Turns out you don't need global references for method IDs.)

Stuart Cook
  • 3,994
  • 25
  • 23
  • Your answer is very useful, but it doesn't help my problem. My global cached jclass and jmethodID are valid,i can reach my JAVA callback, which is a member of that class(its not static). The problem is that i cannot call another member of that class from that callback, because i think the jobject i cache doesn't point to: "com/android/newlineactivity/NewLineGLSurfaceView" because i cache it in another JENV probably. So for a static member this works : env->CallStaticVoidMethod(gJClass, gJMethodID); but for a non-static this doesn't : env->CallVoidMethod(gJObjectCached, gJMethodID);Makes sense? – eKKo82 Sep 01 '11 at 12:08
  • Is `gJObjectCached` a global reference, or did you just store the local reference that was passed into `cachejavaobject`? – Stuart Cook Sep 01 '11 at 12:26
  • Its a global reference and is valid at the time i call env->CallVoidMethod(gJObjectCached, gJMethodID); But that callback() crashes when it calls requestRender() internally. I even tried jobject jobj = env->NewObject(gJClass, gJMethodID); and pass that, in hopes that with the correct env, gjClass and gJMethodID i can call the method and it will work... the methods gets called but crashes again at requestRender(); – eKKo82 Sep 01 '11 at 12:40
  • hmmm... your question its a bit confusing... jobject gJObjectCached; is declared global in my *.cpp file and i store in it the local reference that was passed into cachejavaobject using : JNIEXPORT void Java_com_android_OpenGL_NativeLib_cachejavaobject(JNIEnv* env, jobject obj) { // cache the java object gJObjectCached = env->NewGlobalRef(obj); LogNativeToAndroidExt("gjObjectCached global"); } – eKKo82 Sep 01 '11 at 12:44
  • If you're doing `gJObjectCached = env->NewGlobalRef(obj)` then you're storing a global reference, which is what you should be doing. If things still aren't working after doing that, I'm afraid I'm out of suggestions. – Stuart Cook Sep 01 '11 at 12:51
  • Thank you Stuart for all your follow ups. It helped me get a view on things. I think that a solution would be to move cachejaveobject() and all my other native methods from the NativeLib into the same class : OpenGLSurfaceView. That way obj, and subsequently gJObjectCached will point to the correct location. I will update here if that works in case somebody has this problem. – eKKo82 Sep 01 '11 at 13:09