使用 JNI 在来自 Java 的调用之间将本机对象保存在内存中
Keeping a native object in memory between calls from Java using JNI
我有一个 Android 应用程序,需要使用 C
库。
我正在使用 JNI
与之交互。该库使用一个结构(我们称之为 foo
)。 foo
使用一组初始参数,其中包括指向 C
函数的指针,这些函数用于从我的应用程序请求更多数据,并将该数据合并到计算过程中。一旦它拥有所需的一切,它就会通过 C
回调函数 return 返回一个结果,它还需要一个指向该回调函数的指针。我需要将所有这些 C
回调函数挂接到我的应用程序以从用户那里获取更多数据,return 将这些数据返回到 foo
并最终通过 final 在我的应用程序中向用户显示结果回调函数。
我创建了 foo_callbacks
- 刚刚定义了我在初始化时传递给 foo
的静态 C
函数,在这些函数中我使用 JNI
再次调用我的应用程序(尚未对此进行测试,但我还保留了对 jvm
的引用并从中获取 JNIEnv
并附加到当前线程 like this)。
但是正在发生的事情是这样的:
- 调用
JNI
以使用指向来自 foo_callbacks
的静态函数的指针初始化 foo
。我保留对 foo
. 的静态引用
- 单独调用
JNI
使用现有 foo
对象开始计算过程
- 但是当
foo
需要使用我之前传递的回调函数时,我收到此消息:A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x4 in tid 14244
。
谷歌搜索后,似乎是我正在尝试访问的内存 isn't owned by my app anymore。所以我认为那些对回调函数的引用不再有效。所以我的问题是,如何在 JNI
调用之间将本机对象保存在内存中?还是有另一种方法来解决这个问题?谢谢。
下面是一些示例代码:
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;
}
请注意,这不是经过测试的代码 - 它基本上是我正在尝试做的事情的简单得多的版本。
这是一个多线程问题。无法保证 JNI
将在与 Java
相同的线程上执行本机代码。使所有 native
功能 synchronized
解决了问题。
//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
}
这是服务旨在解决的问题。共有三种风格: 与用户交互的前台服务;在后台做事的后台服务;和绑定服务 (client/server),只要客户端应用程序绑定到它们就存在。将您的 JNI 代码实现为绑定服务,并在其顶部使用 Java 的薄包装;那么你就会得到你的坚持。
我有一个 Android 应用程序,需要使用 C
库。
我正在使用 JNI
与之交互。该库使用一个结构(我们称之为 foo
)。 foo
使用一组初始参数,其中包括指向 C
函数的指针,这些函数用于从我的应用程序请求更多数据,并将该数据合并到计算过程中。一旦它拥有所需的一切,它就会通过 C
回调函数 return 返回一个结果,它还需要一个指向该回调函数的指针。我需要将所有这些 C
回调函数挂接到我的应用程序以从用户那里获取更多数据,return 将这些数据返回到 foo
并最终通过 final 在我的应用程序中向用户显示结果回调函数。
我创建了 foo_callbacks
- 刚刚定义了我在初始化时传递给 foo
的静态 C
函数,在这些函数中我使用 JNI
再次调用我的应用程序(尚未对此进行测试,但我还保留了对 jvm
的引用并从中获取 JNIEnv
并附加到当前线程 like this)。
但是正在发生的事情是这样的:
- 调用
JNI
以使用指向来自foo_callbacks
的静态函数的指针初始化foo
。我保留对foo
. 的静态引用
- 单独调用
JNI
使用现有foo
对象开始计算过程 - 但是当
foo
需要使用我之前传递的回调函数时,我收到此消息:A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x4 in tid 14244
。
谷歌搜索后,似乎是我正在尝试访问的内存 isn't owned by my app anymore。所以我认为那些对回调函数的引用不再有效。所以我的问题是,如何在 JNI
调用之间将本机对象保存在内存中?还是有另一种方法来解决这个问题?谢谢。
下面是一些示例代码:
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;
}
请注意,这不是经过测试的代码 - 它基本上是我正在尝试做的事情的简单得多的版本。
这是一个多线程问题。无法保证 JNI
将在与 Java
相同的线程上执行本机代码。使所有 native
功能 synchronized
解决了问题。
//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
}
这是服务旨在解决的问题。共有三种风格: 与用户交互的前台服务;在后台做事的后台服务;和绑定服务 (client/server),只要客户端应用程序绑定到它们就存在。将您的 JNI 代码实现为绑定服务,并在其顶部使用 Java 的薄包装;那么你就会得到你的坚持。