无法从 JNI 设置 Java int 数组字段

Posted

技术标签:

【中文标题】无法从 JNI 设置 Java int 数组字段【英文标题】:Unable to set Java int array field from JNI 【发布时间】:2019-08-01 12:47:33 【问题描述】:

我正在开发一个 android 应用程序,我正在从 C++ 库中接收相机数据。我需要将此数据从 C++ 发送到 Java 代码。为此,我正在使用 JNI。我可以根据 JNI 和 C++ 数据在 Java 中设置不同的字段(例如相机的名称或类型),但我无法设置 ID 字段,因为它是一个 uint8_t 数组。

我该怎么做?

我已经尝试了几种方法来做到这一点,但每次我都得到一个地址无效的SIGSEGV error。对于我正在使用的其他领域

env->Set<Primitives>Field(jobject, jfieldID, value)

方法,但没有类似 int 数组的方法,有吗? 因此,我尝试通过从我的类中调用一个方法来设置此字段并提供int 数组作为参数,但此函数失败并返回SIGSEGV error

然后,我在网上搜索并尝试通过

设置字段
env->GetObjectField(jobject, jfieldID)

env->SetIntArrayRegion(jintArray, start, end, myIntArray)

但这里第一个方法总是返回 null。

JavaVM * mJVM; //My Java Virtual Machine
jobject mCameraObject, mThreadObject; //Previously initialize to call functions in the right thread

void onReceiveCameraList(void *ptr, uint32_t /*id*/, my::lib::Camera *arrayCamera, uint32_t nbCameras) 

    JNIEnv *env;
    mJVM->AttachCurrentThread(&env, nullptr);
    if (env->ExceptionCheck())
        return;

    //Get Field, Method ID, Object and Class
    jclass cameraClass = env->GetObjectClass(mCameraObject);

    jfieldID camIDField = env->GetFieldID(cameraClass, "idCam", "[I");
    jfieldID camNameField = env->GetFieldID(cameraClass, "label", "Ljava/lang/String;");
    jfieldID camConnectedField = env->GetFieldID(cameraClass, "connected", "Z");
    jfieldID camTypeField = env->GetFieldID(cameraClass, "typeProduit", "B");

    jmethodID camReceptionMID = env->GetMethodID(env->GetObjectClass(mThreadObject), "onCamerasReception", "([Lcom/my/path/models/Camera;)V"); //Java function
    jobjectArray cameraArray = env->NewObjectArray(nbCameras, cameraClass, mCameraObject); //Object return in the functions

    //Put the cameras into the vector
    std::vector<my::lib::Camera> vectorCameras;
    if(!vectorCameras.empty())
        vectorCameras.clear();

    if ((arrayCamera != nullptr) && (nbCameras > 0)) 
        for (uint32_t i = 0; i < nbCameras; ++i) 
            vectorCameras.push_back(arrayCamera[i]);
        
    

    //Set the my::lib::Camera field into Java::Camera
    int c= 0;
    for (auto & cam : vectorCameras)
    
        jobject camera = env->AllocObject(cameraClass); //Object Camera to add in cameraArray object

    // MY DATA TO SET ID FIELD ///
    jint idArray[16];
        for (int i = 0; i < 16 ; ++i) 
            idArray[i] = cam.idCamera.data[i]; // uint8_t cam.idCamera.data[16]
        

    ///////// FIRST WAY  /////////
    jmethodID setIDCamMID = env->GetMethodID(env->GetObjectClass(camera), "setIDCam", "([I)V");
    env->CallVoidMethod(camera, setIDCamMID, idArray);

    ///////// SECOND WAY /////////
        jintArray jintArray1 = (jintArray)env->GetObjectField(camera, camIDField);
        env->SetIntArrayRegion(jintArray1, 0, 16, idArray);

    //Set<Primitives>Field : WORKING
        env->SetObjectField(camera, camNameField, env->NewStringUTF((const char *) cam.labelCamera));
        env->SetBooleanField(camera, camConnectedField, cam.isCameraConnected);
        jbyte type;
        if (cam.typeCamera == my::lib::TYPE_1 || cam.typeCamera == my::lib::TYPE_2 || cam.typeCamera == my::lib::TYPE_3) //type not known in JAVA
            type = 0;
        else
            type = cam.typeCamera;
        env->SetByteField(camera, camTypeField, type);

    //Put camera object into cameraArray object
        env->SetObjectArrayElement(cameraArray, c++, camera);
    //for

    //Call Java method with cameraArray
    env->CallVoidMethod(mThreadObject, camReceptionMID, dpCameraArray);

//onreceiveCamera

谁能告诉我是我弄错了还是用错了方法? 有没有其他方法可以设置这些数据?

【问题讨论】:

您是否正在检查所有您的电话,例如jclass cameraClass = env-&gt;GetObjectClass(mCameraObject);,以确保它们确实有效?在您对异常的初始检查之外,您的代码不会进行错误检查,并且可能会在您不知情的情况下严重失败。因为像使用 jobject mCameraObject, mThreadObject; //Previously initialize to call functions in the right thread 那样缓存对象如果处理不当可能会导致问题。见***.com/questions/2093112/… ID 字段是一个 uint8_t 数组——这就是你在 JNI 的 C++ 端所拥有的。但是在 Java 中这个字段是怎么声明的呢? @AndrewHenle 是的,这是我检查的第一点,所有内容都已初始化并正常工作 @AlexCohn 在 JAva 端,声明为 `int[] idCamera = new int[16]; 【参考方案1】:

这会产生一个 C++ 数组,其中包含 jint 类型的元素:

    // MY DATA TO SET ID FIELD ///
    jint idArray[16];
        for (int i = 0; i < 16 ; ++i) 
            idArray[i] = cam.idCamera.data[i]; // uint8_t cam.idCamera.data[16]
        

了解这不是Java数组很重要。因此,这...

    ///////// FIRST WAY  /////////
    jmethodID setIDCamMID = env->GetMethodID(env->GetObjectClass(camera), "setIDCam", "([I)V");
    env->CallVoidMethod(camera, setIDCamMID, idArray);

... 不正确。对于您尝试调用的 Java 方法的相应参数,idArray 不是正确的类型(并且不会衰减为指向正确类型的指针)。

另一方面,这...

    ///////// SECOND WAY /////////
        jintArray jintArray1 = (jintArray)env->GetObjectField(camera, camIDField);
        env->SetIntArrayRegion(jintArray1, 0, 16, idArray);

... 没问题,只要该字段已经包含对长度至少为 16 的 int[] 的引用。由于它不适合您,我认为该字段的初始值不满足该标准。

如果你需要创建一个 Java int[],那么

    使用JNI 的NewIntArray() 函数,它返回一个具有您指定长度的jintArray。那么 使用适当的 JNI 方法设置元素(见下文),最后 要么使用 SetObjectField() 将数组直接分配给目标对象的字段,要么使用对象的 setter 方法来执行此操作,就像您的第一次尝试一样。

至于设置 Java 数组的元素,SetIntArrayRegion() 可以正常工作(给定一个足够长度的实际 Java 数组),但这确实需要您分配一个单独的本机原语数组(您的 idArray)从中复制值。一种更有效的方法是使用GetPrimitiveArrayCritical() 让Java 提供缓冲区——可能是指向内部数据的直接指针——然后在完成后使用ReleasePrimitiveArrayCritical()。像这样的:

// It is assumed here that the length of the array is sufficient, perhaps because
// we just created this (Java) array.
jint *idArray = (jint *) env->GetPrimitiveArrayCritical(jintArray1, NULL);

for (uint32_t i = 0; i < nbCameras; ++i) 
    idArray[i] = cam.idCamera.data[i];

env->ReleasePrimitiveArrayCritical(jintArray1, idArray, 0);

【讨论】:

正如我在回答中所说,使用AllocObject 会生成一个具有字段默认值的对象,包括camId 字段的null。如果 OP 使用了NewObject,这是构造函数的责任。 谢谢,你的回答很有启发性。它现在正在工作! 这是对AllocObject、@Botje 的一个很好的观察。我们可以确信对象的字段具有其类型的默认值,因为它还没有被初始化。但是当然,即使它已经被初始化,也不一定是有问题的字段包含对合适数组的引用。【参考方案2】:

对于第一种方法,您需要先创建一个Java int[],从idArray 填充它,然后然后调用您的方法:

jintArray j_arr = env->NewIntArray(16);
env->SetIntArrayRegion(j_arr, 0, 16, idArray);
env->CallVoidMethod(camera, setIDCamMID, j_arr);

您的第二种方法不起作用,因为您从未调用填充 idCam 字段的构造函数。 但是,您可以通过 JNI 执行此操作:

env->SetObjectField(camera, camIDField, j_arr);

【讨论】:

以上是关于无法从 JNI 设置 Java int 数组字段的主要内容,如果未能解决你的问题,请参考以下文章

JAVA_JNI字段描述符“([Ljava/lang/String;)V”(Android)

如何将数组从 JNI 返回到 Java?

JNI字段描述符

如何将字节数组从android java类传递给JNI C NDK?

JNI内通过参数形式从C/C++中传递string类型数据至Java层

JNI 无法在 NetBeans 上检测到 __int64