【问题标题】:JNI keeping a global reference to an object, accessing it with other JNI methods. Keeping a C++ object alive across multiple JNI callsJNI 保持对对象的全局引用,并使用其他 JNI 方法访问它。在多个 JNI 调用中保持 C++ 对象处于活动状态
【发布时间】: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;
}

谢谢

【问题讨论】:

    标签: android c++ java-native-interface


    【解决方案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 方法会

    1. 实例化一个对象,
    2. 创建一个唯一的参考编号,
    3. 将引用和对象之间的链接存储在地图中并
    4. 将引用号返回给 Java 上下文。

    在进一步调用时,引用号被传递给 C++ 上下文,

    1. 地图根据参考编号解析对象
    2. 在该对象上执行相应的方法
    3. 传给 Java 上下文的返回值或输出值。

    第三步中的删除方法会从参考地图中删除条目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-12
      相关资源
      最近更新 更多