JNI保留对对象的全局引用,并使用其他JNI方法访问它.在多个JNI调用中保持C++对象的活动状态
JNI keeping a global reference to an object, accessing it with other JNI methods. Keeping a C++ object alive across multiple JNI calls
我刚开始使用JNI,遇到了以下问题。
我有一个C++库,它有一个简单的类。我有三个从Java Android项目调用的JNI方法,它们分别初始化所述类、调用实例化类上的方法和销毁它。我保留了对这个对象的全局引用,所以它在其他两个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;
}
感谢
您的实现的问题是jstring
数据成员。NewStringUTF()
创建一个Java String
对象,以从JNI方法返回。因此,它是Java本地引用。然而,您将其存储在C++对象中,并尝试在JNI调用中使用它。
您应该更好地区分C++对象、Java和两者之间的JNI接口。换句话说,C++应该使用C++方式来存储字符串(如std::string
)。InvokeNativeFunction()
的JNI实现应该将其转换为jstring
作为返回值。
PS:有些情况需要C++实现保留对Java对象的引用(或者反过来)。但如果做得不好,它会使代码更加复杂,并且容易出现内存错误。所以你应该只在它真正增加价值的地方使用它。
我在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类实例的整个生命周期中保持活动。我还使用getFieldId
、SetLongField
和GetLongField
:将指向CppObject
的指针直接存储在JavaClass
中
// 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++中的对象引用,但您也可以存储Java中的jlong
对象指针,如其他答案所述 - 在我的最后一段代码中,我将
JavaObject
类实现为Parcelable
,以便将我的类传递给使用Intent
和附加的多个活动
你不能这么做。对象引用(包括类引用)在JNI调用中无效。您需要阅读JNI规范中关于本地和全局引用的部分。
从这个问题上不太确定,这是否破坏了您的用例:
通常,您希望通过java调用来控制C++对象。当实例化为静态变量或在堆上时,C++对象可以比JNI调用更长寿。
对于单个对象,这很容易:您有创建/修改/删除之类的JNI方法来完成该对象的工作。
当您想要控制一组动态对象时,您需要通过一个唯一的参考号来区分它们。即JNI创建方法将
- 实例化对象
- 创建唯一的参考号
- 将引用和对象之间的链接存储在地图中
- 将引用号返回到Java上下文
在进一步调用时,参考号被传递到C++上下文
- 地图从参考号解析对象
- 在该对象上执行相应的方法
- 传递给Java上下文的返回值或输出值
第三步中的delete方法将从引用映射中删除该条目。
- 使用 jni 将返回带有模板的对象的 Java 代码转换为 c++
- 如何调用传递给 JNI 'jobject' 的 Java 对象的子类/子类的方法
- 将对象传递给 Java C++与使用 JNI 逐个设置对象参数
- JNI 如何将 Java 对象数组传递给相同对象类型的 C++ 数组
- 如何将类对象传递给 jni 本机方法
- JNI返回Java对象,可以返回本地参考,还是必须是全局
- 将 JSON 对象作为参数在 JNI 中传递给 CPP,并在 CPP 中检索 JSON 对象的数据
- Android JNI:Java通过JNI调用C++类对象
- 在 JNI 中创建新对象
- 在创建新对象时,尝试将参数提供给JNI对象的构造函数时会出现错误
- 使用 JNI 从 Java 访问 C++ 对象数组
- JNI Java对象实例化期间的Segfault
- 通过其他线程通过JNI呼叫保存的Java对象
- 有没有人试图通过 JNI 将 protobuf 对象从 C++ 返回到 Java
- JNI:MAP jobject到本机C 对象
- JNI:正确管理Java对象的寿命
- JNI与共享对象有问题
- 正在比较JNI对象引用
- JNI对象创建和内存管理
- 访问JNI对象到Java层作为引用指针