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

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

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

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

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

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

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

Джава:

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

Спасибо


person cierech    schedule 20.03.2012    source источник


Ответы (3)


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

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

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

person Kris Van Bael    schedule 21.03.2012
comment
Вот и все! Я был уверен, что проблема в другом. В любом случае, большая помощь. Большое спасибо! - person cierech; 21.03.2012

Я не смог найти хорошего ответа на SO по этой теме, поэтому вот мое решение для сохранения объектов в 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 для хранения объекта в памяти HEAP, чтобы он оставался активным на протяжении всего жизненного цикла экземпляров класса 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 ++ не имеет сборки мусора, и объект будет жить в памяти HEAP, пока вы не используете delete.
  • Я использую GetFieldID, SetLongField и GetLongField для хранения ссылки на объект из C ++, но вы также можете сохранить указатель объекта jlong из Java, как обсуждалось в других ответах.
  • В моем последнем коде я реализовал класс JavaObject как Parcelable, чтобы передать свой класс нескольким действиям, используя Intent с дополнительными функциями.
person Jaime Ivan Cervantes    schedule 20.06.2017

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

person user207421    schedule 21.03.2012
comment
Это кажется неверным. Я прочитал тот раздел, о котором вы говорите. Я не спрашиваю о локальной / глобальной ссылке на класс Java в собственной библиотеке, а о моем собственном классе C ++. Приведенный выше ответ правильный. - person cierech; 21.03.2012
comment
@ user1282104 В этом нет ничего неправильного. Вы храните jstring в своем объекте C ++ и сохраняете его глобально. Следовательно, вы храните jstring глобально, и вы не можете этого сделать. Другой плакат сказал то же самое. Вы можете использовать GlobalRef или WeakRef для хранения jstring или, как предлагалось на другом плакате, использовать способ хранения строки C ++. - person user207421; 21.03.2012
comment
@downvoter Не обманывай себя. Предлагаю вам прочитать спецификацию самостоятельно. - person user207421; 14.09.2017