JNI 保持对对象的全局引用,并使用其他 JNI 方法访问它。在多个 JNI 调用中保持 C++ 对象处于活动状态
Posted
技术标签:
【中文标题】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 【发布时间】:2012-03-20 23:05:19 【问题描述】:我刚开始使用 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;
谢谢
【问题讨论】:
【参考方案1】:您的实现的问题是jstring
数据成员。 NewStringUTF()
创建一个 Java String
对象以从 JNI 方法返回。所以它是一个 Java 本地引用。但是,您将其存储在 C++ 对象中并尝试在 JNI 调用中使用它。
您应该更好地区分 C++ 对象、Java 和它们之间的 JNI 接口。换句话说,C++ 应该使用 C++ 存储字符串的方式(如std::string
)。 InvokeNativeFunction()
的 JNI 实现应该将其转换为 jstring
作为返回值。
PS:有种情况需要 C++ 实现来保持对 Java 对象的引用(或相反)。但如果处理不当,它会使代码更加复杂并且容易出现内存错误。所以你应该只在真正增加价值的地方使用它。
【讨论】:
就是这样!我确信问题出在其他地方。总之,帮助很大。非常感谢!【参考方案2】:我在这个主题上找不到关于 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++ 的对象引用,但您也可以存储来自Java 的jlong
对象指针,如其他答案中所述。
在我的最终代码中,我将 JavaObject
类实现为 Parcelable
,以便使用带有附加功能的 Intent
将我的类传递给多个活动。
【讨论】:
是否需要类上的 DeleteLocalRef?认为它们在本机调用结束时无论如何都会变得无效/删除。【参考方案3】:你不能那样做。对象引用(包括类引用)在 JNI 调用中无效。您需要阅读 JNI 规范中有关本地和全局引用的部分。
【讨论】:
这似乎不正确。我确实阅读了您所指的那部分。我不是在询问本地库中 Java 类的本地/全局引用,而是我自己的 C++ 类。上面的答案是正确的。 @user1282104 这没什么不对的。您将 jstring 存储在 C++ 对象中,并将其存储在全局范围内。因此,您在全局存储 jstring,而您不能这样做。另一张海报也说了同样的话。您可以使用 GlobalRef 或 WeakRef 来保存 jstring,或者,正如其他海报所建议的那样,使用 C++ 存储字符串的方式。 @downvoter 不要自欺欺人。我建议您自己阅读规范。【参考方案4】:从这个问题中不确定,这是否符合您的用例:
通常,您希望通过 java 调用来控制 C++ 对象。 当实例化为静态变量或在堆上时,C++ 对象的寿命可能比 JNI 调用长。
对于单个对象,这很容易:您可以使用创建/修改/删除等 JNI 方法来处理该对象。
当您想要控制一组动态对象时,您需要通过唯一的参考编号来区分它们。 IE。 JNI-create 方法会
-
实例化一个对象,
创建一个唯一的参考编号,
将引用和对象之间的链接存储在地图中并
将引用号返回给 Java 上下文。
在进一步调用时,引用号被传递给 C++ 上下文,
-
地图根据参考编号解析对象
在该对象上执行相应的方法
传给 Java 上下文的返回值或输出值。
第三步中的删除方法会从参考地图中删除条目。
【讨论】:
以上是关于JNI 保持对对象的全局引用,并使用其他 JNI 方法访问它。在多个 JNI 调用中保持 C++ 对象处于活动状态的主要内容,如果未能解决你的问题,请参考以下文章
Android JNI 学习:JNI 接口整理 — References Api
JNI/NDK开发指南——JNI局部引用全局引用和弱全局引用