Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to set Java int array field from JNI

I'm developing an Android App and I'm receiving camera data from a lib in C++. I need to send this data from C++ to the Java code. For this, I'm using JNI. I'm able to set different fields in Java from the JNI and the C++ data (like the name or the type of the camera), but I'm unable to set the ID Field because it's an uint8_t array.

How can I do this?

I already tried several ways to do this but each time I've got an SIGSEGV error with an invalid address. For others fields I'm using

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

method but there are no methods like that for int array, are there? So, I've tried to set this field by calling a method from my class and provide the int array as parameter but this function failed and returned the SIGSEGV error.

Then, I searched on the web and I tried to set the field through

env->GetObjectField(jobject, jfieldID)

and

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

but here the first method returns always 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

Can someone tell me if I made a mistake or am using it the wrong way?
Is there any other way to set this data?

like image 228
sitivic Avatar asked Mar 03 '23 15:03

sitivic


1 Answers

This yields a C++ array with elements of type 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]
        }

It is important to understand that that is not a Java array. Therefore, this ...

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

... is incorrect. idArray is not the right type (and does not decay to a pointer to the right type) for the corresponding parameter to the Java method you are trying to invoke.

On the other hand, this ...

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

... is ok, provided that the field already contains a reference to an int[] of length at least 16. Since it didn't work for you, I take it that the field's initial value does not satisfy that criterion.

If you need to create a new Java int[], then

  1. use JNI's NewIntArray() function, which returns a jintArray with the length you specify. Then
  2. use the appropriate JNI methods for setting the elements (see below), and finally
  3. either assign the array directly to the target object's field with SetObjectField() or use the object's setter method to do so, as in your first attempt.

As for setting elements of the Java array, SetIntArrayRegion() will work fine for that (given an actual Java array of sufficient length), but that does require you to allocate a separate native array of primitives (your idArray) from which to copy the values. A slightly more efficient approach would be to use GetPrimitiveArrayCritical() to let Java provide the buffer -- possibly a direct pointer to the internal data -- and then ReleasePrimitiveArrayCritical() when you're done. Something like this:

// 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);
like image 160
John Bollinger Avatar answered Mar 11 '23 05:03

John Bollinger