JNI сохраняет глобальную ссылку на объект, получая к нему доступ с помощью других методов JNI. Сохранение объекта C++ в нескольких вызовах JNI

Я только начинаю с JNI, и у меня есть следующая проблема.

у меня есть библиотека C++, которая имеет простой класс. У меня есть три метода JNI, вызванные из проекта Java Android, который, instatiate указанный класс, вызывает метод для экземпляра класса и уничтожает его, соответственно. Я сохраняю глобальную ссылку на этот объект, поэтому он будет доступен для меня в двух других методах JNI.

Я подозреваю, что не могу этого сделать. Когда я запускаю приложение, я получаю ошибку выполнения (используется устаревшая ссылка), и я подозреваю, что это связано с тем, что глобальная ссылка недействительна при последующих вызовах других методов JNI.

это единственный способ достичь того, что я хочу (чтобы объект жил через несколько вызовов JNI), фактически передать указатель на экземпляр класса обратно на Java, сохранить его там, а затем передать его обратно в функции JNI? Если да, то все в порядке, я хочу убедиться, что не могу сделать это с глобальной ссылкой, и я не просто что-то упускаю.

Я прочитал документацию и главы о глобальных/локальных ссылках в JNI, но похоже, что это относится только к классам Java, а не к моим собственным, родным классам C++, или я ошибаюсь.

вот код, если мое описание не ясно (подводя итог, мне интересно, будет ли этот механизм сохранения объектов работать вообще):

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:

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, у тело:

#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++:

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

 TestClass(JNIEnv *env);

 jstring getString();
};

тело 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;
}

спасибо

3 ответов


проблема с вашей реализации -jstring член данных. NewStringUTF() создает Java String объект для возврата из метода JNI. Таким образом, это локальная ссылка Java. Однако вы сохраняете это внутри объекта C++ и пытаетесь использовать его в вызовах JNI.

вы должны держать лучшее различие между объектами C++, Java и интерфейсом JNI между ними. Другими словами, C++ должен использовать способ хранения строк на C++ (например,std::string). Реализация JNI InvokeNativeFunction() следует преобразовать это в jstring в качестве возвращаемого значения.

PS: есть are случаи, которые требуют реализации C++ для сохранения ссылок на объекты Java (или наоборот). Но это делает код более сложным и склонным к ошибкам памяти, если не сделано правильно. Поэтому вы должны использовать его только там, где он действительно добавляет ценность.


я не смог найти хороший ответ на эту тему, поэтому вот мое решение сохранить объекты на C++, чтобы ссылаться на них из нескольких вызовов JNI:

Java

на стороне Java я создаю класс с long указатель, чтобы сохранить ссылку на объект C++. Упаковка методов C++ в класс Java позволяет использовать методы C++ в нескольких действиях. Обратите внимание, что я создаю объект C++ в конструкторе, и я удаление объекта при очистке. Это очень важно для того, чтобы предотвратить утечку памяти:

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++

на стороне C++ я определяю функции для создания, изменения и удаления объекта. Важно отметить, что мы должны использовать new и delete для хранения объекта в памяти кучи, чтобы сохранить его в течение всего жизненного цикла экземпляров класса Java. Я также сохраняю указатель на CppObject прямо в JavaClass, используя getFieldId, SetLongField и 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;
    } 

}

Примечания:

  • в отличие от Java, в C++ нет сборки мусора, и объект будет жить в памяти, пока вы не используете delete.
  • я использую GetFieldID, SetLongField и GetLongField для хранения ссылки на объект из C++, но вы также можете сохранить jlong указатель объекта из Java, как обсуждалось в других ответах.
  • на моем окончательном коде я реализовал JavaObject класс как Parcelable для того, чтобы пройти мой класс в несколько действий, используя Intent С дополнительными функциями.

вы не можете этого сделать. Ссылки на объекты, включая ссылки на классы, недопустимы для вызовов JNI. Вам нужно прочитать раздел спецификации JNI о локальных и глобальных ссылках.