Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to get JNIEnv* value in arbitrary context

I have an issue with the NDK.

In my JNI_OnLoad method, I cache the JavaVm pointer, the class that called the method, and a method id which I use later on:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved){
    JNIEnv *env;
    cachedJVM = jvm;
    if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6)){
        LOG_ERROR("Could not get JNIEnv*");
        return JNI_ERR;
    }
    javaClass = (*env)->FindClass(env, "org/test/opensl/AudioProcessor");
    if(javaClass == NULL){
        LOG_ERROR("Could not get java class");
        return JNI_ERR;
    }
    javaCallbackMID = (*env)->GetMethodID(env, javaClass, "enqueueAudio", "([B)V");
    if(javaCallbackMID == NULL){
        LOG_ERROR("Could not get method identifier");
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

I have a small utility method defined as follows that should get me a pointer to the JNIEnv:

JNIEnv* JNU_GetEnv(){
    JNIEnv* env;
    (*cachedJVM)->GetEnv(cachedJVM, (void**)&env, JNI_VERSION_1_6);
    return env;
}

And finally, I have a callback from an OpenSL ES SLAndroidSimpleBufferQueueItf which I want to handle the recorded audio from a SLRecordItf:

void recorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){
    SLresult result;
    JNIEnv* env;
    recorderContext* thisContext = (recorderContext*)context;
    env = JNU_GetEnv();
    if(env == NULL){
        LOG_ERROR("Could not get JNIEnv*");
        return;
    }
    jbyteArray data = (*env)->NewByteArray(env, MAX_PACKET_SIZE);
    if(data == NULL){
        LOG_ERROR("No memory could be allocated for buffer");
        return;
    }
    (*env)->SetByteArrayRegion(env, data, 0, MAX_PACKET_SIZE, recorderBuffer);
    (*env)->CallByteMethodA(env, thisContext->caller, javaCallbackMID, data);
    (*env)->DeleteLocalRef(env, data);
    result = (*bq)->Enqueue(bq, recorderBuffer,
                            RECORDER_FRAMES * sizeof(jbyte));
    checkError(result, "Unable to enqueue new buffer");
}

Where the context parameter for the callback method only holds a reference to the object that called the native method. It is a self defined struct like this:

typedef struct recorderContext{
    jobject caller;
} recorderContext;

However, every time I try to run this, I get the "Could not get JNIEnv*" error message from the callback method.

My question basically comes down to this: Why can I get a pointer to the JNIEnv in the JNI_OnLoad method, but not in the recorderCallback, as both use the same Java VM pointer to get the JNIEnv?

I need this callback to pass the recorded Audio back up to my Java layer for further processing...

like image 368
ThaMe90 Avatar asked May 13 '11 12:05

ThaMe90


People also ask

What is JNIEnv?

JNIEnv – a structure containing methods that we can use our native code to access Java elements. JavaVM – a structure that lets us manipulate a running JVM (or even start a new one) adding threads to it, destroying it, etc…

What is an API invocation?

The Invocation API, which is part of the Java™ Native Interface (JNI), allows non-Java code to create a Java virtual machine, and load and use Java classes. This function lets a multithreaded program make use of Java classes that are running in a single Java virtual machine in multiple threads.

How does JNI work on Android?

It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++). JNI is vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.


1 Answers

Why can I get a pointer to the JNIEnv in the JNI_OnLoad method, but not in the recorderCallback, as both use the same Java VM pointer to get the JNIEnv?

Because the callback happens on some native thread, different from the VM thread which loads the library. The JNI implementation maintains a JNIEnv per thread, and puts the pointer in thread-local storage. It is only initialized for native threads which are attached to the VM. You need to call AttachCurrentThread() (or more probably AttachCurrentThreadAsDaemon()) inside the callback to get a JNIEnv pointer valid for that thread. This attaches the thread to the VM on the first call and is a nop thereafter.

See http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html

Caveat: This answer assumes proper Java. The problems you're seeing suggest that Dalvik behaves the same as the JVM.

like image 84
fizzer Avatar answered Sep 19 '22 02:09

fizzer