android - studio - JNI mantiene una referencia global a un objeto, accediendo a él con otros métodos JNI. Mantener vivo un objeto C++ en múltiples llamadas JNI
jni android (3)
Estoy comenzando con JNI y tengo un problema siguiente.
Tengo una biblioteca C ++ que tiene una clase simple. Tengo tres métodos JNI llamados desde el proyecto Java de Android que instalan dicha clase, llaman a un método en la clase instanciada y lo destruyen, respectivamente. Guardo una referencia global a este objeto, por lo que estaría disponible para mí en los otros dos métodos JNI.
Sospecho que no puedo hacer esto. Cuando ejecuto la aplicación, obtengo un error de tiempo de ejecución (uso pasado de referencia usado), y sospecho que esto se debe a que la referencia global no es válida en llamadas posteriores a otros métodos JNI.
¿Es la única forma de lograr lo que quiero (hacer que el objeto viva a través de múltiples llamadas JNI), para pasar de nuevo el puntero a la clase instanciada de vuelta a Java, mantenerlo allí y luego volver a pasarlo a las funciones JNI? Si es así, está bien, quiero asegurarme de que no puedo hacerlo con una referencia global, y no solo estoy perdiendo algo.
He leído la documentación y los capítulos sobre referencias globales / locales en JNI, pero parece que eso solo se aplica a las clases de Java, y no a las mías, las clases nativas de C ++, o estoy equivocado.
Aquí está el código si mi descripción no es clara (resumiendo, me pregunto si este mecanismo de objetos persistentes funcionará en absoluto):
Java:
package com.test.ndktest;
import android.app.Activity;
import android.os.Bundle;
import android.app.AlertDialog;
public class NDKTestActivity extends Activity {
static {
System.loadLibrary("ndkDTP");
}
private native void initializeTestClass();
private native void destroyTestClass();
private native String invokeNativeFunction();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initializeTestClass();
String hello = invokeNativeFunction();
destroyTestClass();
new AlertDialog.Builder(this).setMessage(hello).show();
}
}
Encabezado JNI:
extern "C" {
jstring Java_com_test_ndktest_NDKTestActivity_initializeTestClass(JNIEnv* env, jobject javaThis);
jstring Java_com_test_ndktest_NDKTestActivity_destroyTestClass(JNIEnv* env, jobject javaThis);
jstring Java_com_test_ndktest_NDKTestActivity_invokeNativeFunction(JNIEnv* env, jobject javaThis);
};
Cuerpo JNI:
#include <string.h>
#include <jni.h>
#include <ndkDTP.h> //JNI header
#include <TestClass.h> //C++ header
TestClass *m_globalTestClass;
void Java_com_test_ndktest_NDKTestActivity_initializeTestClass(JNIEnv* env, jobject javaThis) {
m_globalTestClass = new TestClass(env);
}
void Java_com_test_ndktest_NDKTestActivity_destroyTestClass(JNIEnv* env, jobject javaThis) {
delete m_globalTestClass;
m_globalTestClass = NULL;
}
jstring Java_com_test_ndktest_NDKTestActivity_invokeNativeFunction(JNIEnv* env, jobject javaThis) {
jstring testJS = m_globalTestClass->getString();
return testJS;
}
Encabezado C ++:
class TestClass
{
public:
jstring m_testString;
JNIEnv *m_env;
TestClass(JNIEnv *env);
jstring getString();
};
Cuerpo C ++:
#include <jni.h>
#include <string.h>
#include <TestClass.h>
TestClass::TestClass(JNIEnv *env){
m_env = env;
m_testString = m_env->NewStringUTF("TestClass: Test string!");
}
jstring TestClass::getString(){
return m_testString;
}
Gracias
No puedes hacer eso. Las referencias a objetos, incluidas las referencias de clase, no son válidas en llamadas JNI. Debe leer la sección de la Especificación JNI sobre referencias locales y globales.
El problema con su implementación es el miembro de datos jstring
. NewStringUTF()
crea un objeto Java String
para devolver desde un método JNI. Entonces es una referencia local de Java. Sin embargo, está almacenando esto dentro de un objeto C ++ e intenta usarlo a través de llamadas JNI.
Debe mantener una mejor distinción entre los objetos C ++, Java y la interfaz JNI en el medio. En otras palabras, C ++ debe usar una forma C ++ de almacenar cadenas (como std::string
). La implementación de JNI de InvokeNativeFunction()
debería convertir eso en un jstring
como valor de retorno.
PD: hay casos que requieren que la implementación C ++ mantenga referencias a objetos Java (o viceversa). Pero hace que el código sea más complejo y propenso a errores de memoria si no se hace bien. Por lo tanto, solo debe usarlo cuando realmente agrega valor.
No pude encontrar una buena respuesta en SO sobre este tema, así que aquí está mi solución para mantener los objetos vivos en C ++ con el fin de hacer referencia a ellos a partir de múltiples llamadas JNI:
Java
En el lado de Java, estoy creando una clase con un puntero long
para mantener una referencia al objeto C ++. Envolviendo los métodos C ++ en una clase Java, nos permite usar los métodos C ++ en múltiples actividades. Observe que estoy creando el objeto C ++ en el constructor, y estoy eliminando el objeto en la limpieza. Esto es muy importante para evitar fugas de memoria:
public class JavaClass {
// Pointer (using long to account for 64-bit OS)
private long objPtr = 0;
// Create C++ object
public JavaClass() {
createCppObject();
}
// Delete C++ object on cleanup
public void cleanup() {
deleteCppObject();
this.objPtr = 0;
}
// Native methods
public native void createCppObject();
public native void workOnCppObject();
public native void deleteCppObject();
// Load C++ shared library
static {
System.loadLibrary("CppLib");
}
}
C ++
En el lado de C ++, estoy definiendo funciones para crear, modificar y eliminar el objeto. Es importante mencionar que debemos usar new
y delete
para almacenar el objeto en la memoria HEAP para mantenerlo activo durante todo el ciclo de vida de las instancias de la clase Java. También estoy almacenando el puntero a CppObject
directamente en JavaClass
, usando getFieldId
, SetLongField
y GetLongField
:
// Get pointer field straight from `JavaClass`
jfieldID getPtrFieldId(JNIEnv * env, jobject obj)
{
static jfieldID ptrFieldId = 0;
if (!ptrFieldId)
{
jclass c = env->GetObjectClass(obj);
ptrFieldId = env->GetFieldID(c, "objPtr", "J");
env->DeleteLocalRef(c);
}
return ptrFieldId;
}
// Methods to create, modify, and delete Cpp object
extern "C" {
void Java_com_test_jnitest_JavaClass_createCppObject(JNIEnv *env, jobject obj) {
env->SetLongField(obj, getPtrFieldId(env, obj), (jlong) new CppObject);
}
void Java_com_test_jnitest_JavaClass_workOnCppObject(JNIEnv *env, jobject obj) {
CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));
// Write your code to work on CppObject here
}
void Java_com_test_jnitest_JavaClass_deleteCppObject(JNIEnv *env, jobject obj) {
CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));
delete cppObj;
}
}
NOTAS:
- A diferencia de Java, C ++ no tiene recolección de basura, y el objeto vivirá en la memoria HEAP, hasta que use
delete
. - Estoy usando
GetFieldID
,SetLongField
yGetLongField
para almacenar la referencia del objeto de C ++, pero también puede almacenar el puntero del objetojlong
de Java como se discutió en otras respuestas. - En mi código final, implementé la clase
JavaObject
comoParcelable
para pasar mi clase a múltiples actividades usandoIntent
con extras.