无法从 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->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)
如何将字节数组从android java类传递给JNI C NDK?