JNI хранит глобальную ссылку на объект, обращаясь к нему с помощью других методов JNI. Поддержание живого объекта C ++ в нескольких вызовах JNI - PullRequest
9 голосов
/ 21 марта 2012

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

У меня есть библиотека C ++, которая имеет простой класс.У меня есть три метода JNI, вызванных из проекта Java Android, которые, инстанцируя указанный класс, вызывают метод инстанцированного класса и уничтожают его соответственно.Я сохраняю глобальную ссылку на этот объект, поэтому он будет доступен для меня в двух других методах 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 ]

4 голосов
/ 21 марта 2012

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

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

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

4 голосов
/ 20 июня 2017

Я не смог найти хорошего ответа на 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 с дополнительными функциями.
0 голосов
/ 21 марта 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...