C0D3LIC1OU5 C0D3LIC1OU5 - 7 months ago 35
Java Question

Keeping a native object in memory between calls from Java using JNI

I have an Android app, and need to use a

C
library.
I am using
JNI
to interface with it. That library uses a struct (lets call it
foo
).
foo
works with an initial set of parameters, and among them pointers to
C
functions that it uses to request more data from my app, incorporating that data into the computational process. Once it has everything it needs, it returns a result via a
C
callback function, to which it also needs a pointer. I need to hook all those
C
callback functions to my app to get more data from user, return that data back into
foo
and finally display the results to the user in my app through final callback function.

I created
foo_callbacks
- just defined static
C
functions that I pass into
foo
upon initialization, and in those functions I use
JNI
to call my app again (haven't tested this yet, but I also keep a reference to
jvm
and get
JNIEnv
from that and attach to current thread, like this).

But is what's happening:


  1. Call to
    JNI
    to initialize
    foo
    with pointers to static funcitons from
    foo_callbacks
    . I keep a static reference to
    foo
    .

  2. Separate call to
    JNI
    starts computational process using existing
    foo
    object

  3. But when
    foo
    needs to use a callback function I passed earlier, I get this message:
    A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x4 in tid 14244
    .



Upon Googling, it seems the memory I am trying to access isn't owned by my app anymore. So I think those references to callback funcitons are not valid anymore. So my question is, how do I keep a native object in memory between
JNI
calls? Or is there another way to approach this issue? Thanks.

Here's some sample code:

FooManager.java


...
static {
System.loadLibrary("FooLib");
}

//initialized Foo library
private native int fooLibInit();

//start the process
public native int fooStart(String message);

//continue the process after a delay
public native int fooContinue(String message);

//retrieve milliseconds to schedule next event
public native long fooGetMsToNextEvent();

//method that gets called from native code
public static long getCurrentTime(){
return System.currentTimeMillis();
}

//method that gets called from native code, returning results
public static void deliverResult(String result){
//display result to the user
}

...


FooScheduler.java


...
public static void kickItOff(String message){
FooManager.fooLibInit();
long timeToWait = FooManager.fooGetMsToNextEvent();
//this call figures out what step it is and gets some data
SchedulerUtil.scheduleCallBack(timeToWait);
}

//this is a callback function that gets called after given about of time by SchedulerUtil
public static void callBack(int step, String message){
if(step == 1)
FooManager.fooStart(message)
else FooManager.fooContinue(message);
}
...


FooLib.cpp


#include <string.h>
#include <jni.h>
#include <android/log.h>

extern "C" {
#include "blackbox.h"
#include "foo_wrapper.h"
}

extern "C" {

static jboolean isJni();
//struct defined in blackbox.h
static foo foo_obj;

JNIEXPORT jint
JNI_OnLoad(JavaVM *vm, void *reserved) {
//defined in foo_wrapper.h
set_java_env(vm);
return JNI_VERSION_1_6;
}

JNIEXPORT jint JNICALL
Java_com_myapp_fooInit(JNIEnv * env, jobject obj){
//foo_get_global_time_wrapper and foo_return_result_wrapper functions is defined in foo_wrapper.h.
//those pointers are actually a member variables of foo_obj,
//they gets assigned in the fooInit() so foo_obj can use them later. fooInit is defined in blackbox.h
int resultInit = fooInit(&foo_obj, foo_get_global_time_wrapper, foo_return_result_wrapper);
return resultInit;
}

JNIEXPORT jint JNICALL
Java_com_myapp_fooStart(JNIEnv * env, jobject obj, jstring message){
jboolean copy = isJni();
const char *firstCharPointer = env->GetStringUTFChars(message, &copy);

//here is where the foo_get_global_time_wrapper function is called, and
//
//I am getting A/libc: Fatal signal 11 (SIGSEGV) error.
//
//fooStart is defined in blackbox.h
int resultCode = fooStart(&foo_obj, (uint8*)firstCharPointer, strlen(firstCharPointer));
return resultCode;
}

JNIEXPORT jint JNICALL
Java_com_myapp_fooContinue(JNIEnv * env, jobject obj, jstring message){
jboolean copy = isJni();
const char *firstCharPointer = env->GetStringUTFChars(chunk, &copy);

//here blackbox produces results based on the first and second messages that were passed in and calls foo_return_result_wrapper with results
//fooContinue is defined in blackbox.h
int resultCode = fooContinue(&foo_obj, (uint8*)firstCharPointer, strlen(firstCharPointer));
return resultCode;
}


static jboolean isJni(){
return JNI_TRUE;
}
}


foo_wrapper.c


#include "foo_wrapper.h"
#include <jni.h>
#include <string.h>

static JavaVM *JVM;

extern uint32 foo_get_global_time_wrapper() {
JNIEnv *env;
int result = (*JVM)->GetEnv(JVM, (void **) &env, JNI_VERSION_1_6);
if (result != JNI_OK) {
LOGI("couldnt get JVM.");
return 1;
}

jclass clazz = (*env)->FindClass(env, "com/myapp/FooManager");
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "getCurrentTime", "()J");
long milliseconds;
(*env)->CallStaticObjectMethod(env, clazz, mid, milliseconds);
return milliseconds;
}

extern int foo_return_result_wrapper(const uint8 *start, uint16 length) {
JNIEnv *env;
int result = (*JVM)->GetEnv(JVM, (void **) &env, JNI_VERSION_1_6);
if (result != JNI_OK) {
LOGI("couldnt get JVM.");
return 1;
}

jstring result = //get jstring from parameters start and length;

jclass clazz = (*env)->FindClass(env, "com/myapp/FooManager");
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "deliverResult", "(LJava.lang.String;)J");

jobject obj = (*env)->CallStaticObjectMethod(clazz, mid, result);
return 0;
}

extern void set_java_env(JavaVM *vm) {
JVM = vm;
}


Please note, this isn't tested code - it basically a much simpler version of what I am trying to do.

Answer

It was a multithreading issue. There is no guarantee that JNI will execute native code on the same thread as Java. Making all native functions synchronized resolved the issue.

//initialized Foo library
private native synchronized int fooLibInit();

//start the process 
public native synchronized int fooStart(String message);

//continue the process after a delay 
public native synchronized int fooContinue(String message);

//retrieve milliseconds to schedule next event
public native synchronized long fooGetMsToNextEvent();

//method that gets called from native code
public static synchronized long getCurrentTime(){
    return System.currentTimeMillis();
}

//method that gets called from native code, returning results
public static synchronized void deliverResult(String result){
    //display result to the user
}