JNI保持对对象的全局引用,使用其他JNI方法访问它.在多个JNI调用中保持C++对象的活动

cie*_*ech 9 c++ java-native-interface android

我刚刚开始使用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();
}
Run Code Online (Sandbox Code Playgroud)

}

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);

};
Run Code Online (Sandbox Code Playgroud)

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;

}
Run Code Online (Sandbox Code Playgroud)

C++标题:

class TestClass
{
 public:
 jstring m_testString;
 JNIEnv *m_env;

 TestClass(JNIEnv *env);

 jstring getString();
};
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

谢谢

Kri*_*ael 5

您的实现的问题是jstring数据成员。NewStringUTF()创建一个 JavaString对象以从 JNI 方法返回。所以它是一个Java本地引用。但是,您将其存储在 C++ 对象中并尝试在 JNI 调用中使用它。

您应该更好地区分 C++ 对象、Java 和介于两者之间的 JNI 接口。换句话说,C++ 应该使用 C++ 存储字符串的方式(如std::string)。的 JNI 实现InvokeNativeFunction()应该将其转换为 ajstring作为返回值。

PS:有需要的C ++实现,以保持引用Java对象(或者反过来)的情况。但是如果做得不好,它会使代码更加复杂并且容易出现内存错误。所以你应该只在它真正增加价值的地方使用它。


Jai*_*tes 5

我在这个主题上找不到关于 SO 的好答案,所以这是我的解决方案,使对象在 C++ 上保持活动状态,以便从多个 JNI 调用中引用它们:

爪哇

在 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");
    }

}
Run Code Online (Sandbox Code Playgroud)

C++

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

}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 与 Java 不同,C++ 没有垃圾收集,并且对象将存在于 HEAP 内存中,直到您使用delete.
  • 我正在使用GetFieldID,SetLongFieldGetLongField来存储来自 C++ 的对象引用,但您也可以jlong像其他答案中讨论的那样存储来自 Java的对象指针。
  • 在我的最终代码中,我将JavaObject类实现为 aParcelable以便将我的类传递给使用Intent附加功能的多个活动。