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:
- Call to
JNI
to initializefoo
with pointers to static funcitons fromfoo_callbacks
. I keep a static reference tofoo
. - Separate call to
JNI
starts computational process using existingfoo
object - 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, ©);
//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, ©);
//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.