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 о локальных и глобальных ссылках.