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++ 方面,我定义了创建、修改和删除对象的函数。值得一提的是,我们必须使用newdelete 将对象存储在HEAP 内存中,以使其在Java 类实例的整个生命周期中保持活动状态。我还将指向CppObject 的指针直接存储在JavaClass 中,使用getFieldIdSetLongFieldGetLongField

// 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。 我使用GetFieldIDSetLongFieldGetLongField 来存储来自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 第九篇之Jni总结

混合编程jni 第九篇之Jni总结

JNI/NDK开发指南——JNI局部引用全局引用和弱全局引用

JNI/NDK开发指南——JNI局部引用全局引用和弱全局引用

JNI官方中文资料