2012-03-20 11 views
8

Ho appena iniziato con JNI e ho un problema seguente.JNI mantenendo un riferimento globale a un oggetto, accedendo ad esso con altri metodi JNI. Mantenere vivo un oggetto C++ su più chiamate JNI

Ho una libreria C++ che ha una classe semplice. Ho tre metodi JNI chiamati dal progetto Java Android che, instatiate la classe, chiamano un metodo sulla classe istanziata e lo distruggono, rispettivamente. Mantengo un riferimento globale a questo oggetto, quindi sarebbe disponibile per me negli altri due metodi JNI.

Sospetto di non poterlo fare. Quando eseguo l'app, ottengo un errore di runtime (riferimento usato stantio), e sospetto che ciò sia dovuto al fatto che la referenza globale non è valida alle chiamate successive ad altri metodi JNI.

È l'unico modo per ottenere ciò che voglio (avere l'oggetto in diretta su più chiamate JNI), in realtà passare il puntatore alla classe istanziata su Java, tenerlo in giro e quindi passarlo di nuovo al Funzioni JNI? Se è così, va bene, voglio essere sicuro di non poterlo fare con un riferimento globale, e non mi manca solo qualcosa.

Ho letto la documentazione ei capitoli relativi ai riferimenti globali/locali in JNI, ma sembra che ciò si applichi solo alle classi Java e non alle mie classi native C++ o mi sbaglio.

Ecco il codice se la mia descrizione non è chiara (riassumendo, mi chiedo se questo meccanismo di persistenza degli oggetti funziona affatto):

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(); 
} 

}

JNI intestazione:

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); 

}; 

JNI corpo:

#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; 

} 

C++ intestazione:

class TestClass 
{ 
public: 
jstring m_testString; 
JNIEnv *m_env; 

TestClass(JNIEnv *env); 

jstring getString(); 
}; 

C++ corpo:

#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; 
} 

Grazie

risposta

4

Il problema con l'implementazione è il membro di dati jstring. NewStringUTF() crea un oggetto Java String da restituire da un metodo JNI. Quindi è un riferimento locale Java. Tuttavia, lo stai memorizzando all'interno di un oggetto C++ e prova a utilizzarlo attraverso le chiamate JNI.

Si dovrebbe mantenere una migliore distinzione tra gli oggetti C++, Java e l'interfaccia JNI in mezzo. In altre parole, il C++ dovrebbe usare un modo C++ di memorizzare stringhe (come std::string). L'implementazione JNI di InvokeNativeFunction() dovrebbe convertirla in un valore jstring come valore di ritorno.

PS: Lì sono casi che richiedono l'implementazione C++ per mantenere i riferimenti agli oggetti Java (o viceversa).Ma rende il codice più complesso e incline a bug di memoria se non fatto bene. Quindi dovresti solo usarlo dove aggiunge davvero valore.

+0

Ecco fatto! Ero sicuro che il problema fosse altrove. Comunque, un grande aiuto. Molte grazie! – cierech

0

Non puoi farlo. I riferimenti agli oggetti, inclusi i riferimenti alle classi, non sono validi per tutte le chiamate JNI. È necessario leggere la sezione della Specifica JNI sui riferimenti locali e globali.

+0

Questo sembra non essere corretto. Ho letto quella sezione a cui ti riferisci. Non sto chiedendo un riferimento locale/globale di una classe Java nella libreria nativa, ma la mia classe C++. La risposta sopra è corretta. – cierech

+1

@ user1282104 Non c'è niente di sbagliato in proposito. Stai memorizzando una jstring nel tuo oggetto C++ e memorizzandola globalmente. Quindi stai memorizzando la stringa jstring a livello globale e non puoi farlo. L'altro poster ha detto la stessa cosa. È possibile utilizzare un GlobalRef o WeakRef per mantenere la jstring o, come suggerito dall'altro poster, utilizzare un modo C++ di memorizzare la stringa. – EJP

+0

Ok, sono corretto. Scusate. – cierech

0

non riuscivo a trovare una buona risposta su SO su questo argomento, ecco la mia soluzione per mantenere gli oggetti vivo su C++ al fine di riferimento da più JNI chiamate:

Java

On il lato Java, sto creando una classe con un puntatore long per mantenere un riferimento all'oggetto C++. Disporre i metodi C++ in una classe Java, ci consente di utilizzare i metodi C++ in più attività. Si noti che sto creando l'oggetto C++ sul costruttore e sto eliminando l'oggetto durante la pulizia. Questo è molto importante al fine di evitare perdite di 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++

Sul lato ++ C, sto definendo funzioni per creare, modificare e eliminare l'oggetto. È importante ricordare che dobbiamo usare new e delete per memorizzare l'oggetto nella memoria HEAP per tenerlo in vita per tutto il ciclo di vita delle istanze della classe Java. Sono anche la memorizzazione del puntatore CppObject dritto in JavaClass, utilizzando getFieldId, SetLongField, e 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; 
    } 

} 

NOTE:

  • A differenza di Java, C++ non ha raccolta dei rifiuti, e la l'oggetto vivrà nella memoria HEAP fino a quando non si utilizza delete.
  • Sto usando GetFieldID, SetLongField e GetLongField per memorizzare il riferimento all'oggetto da C++, ma è possibile anche memorizzare il puntatore di oggetto jlong da Java come discusso in altre risposte.
  • Sul mio codice finale, ho implementato la classe JavaObject come Parcelable per passare la mia classe a più attività utilizzando Intent con extra.