将二维数组从 C++ 传递到 Java 并返回到 C++
Passing 2d array from C++ to Java and back to C++
如何使用 JNI 将二维数组从 C++ 传递到 Java 并从 return 传回 C++?
Sample2.java
public static int[][] intArrayMethod(int[][] n){
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
n[i][j] = 2;
}
}
return n;
}
CppSample.cpp
int main(){
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
jint cells[10][10]; // my 2d array
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
cells[i][j] = 1;
}
}
if (status != JNI_ERR){
cls = env->FindClass("Sample2");
if (cls != 0){
mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");
if (mid != 0){
cells = env->CallStaticIntMethod(cls, mid, cells);
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
printf("%d", cells[i][j]);
}
}
}
}
jvm->DestroyJavaVM();
return 0;
}
else{
return 1;
}
}
我不确定在 C++ 和 java 之间传递二维数组的正确方法是什么。
希望大家多多指导,谢谢!
这与其说是困难,不如说是乏味。普遍的问题是 Java 中的数组与 C++ 中的数组完全不同,并且(某种程度上)更类似于不可调整大小的向量。无法将 C++ 数组直接传递给 Java1,因为 Java 不知道如何处理它。所以我们需要转换函数,例如:
将二维数组从 C++ 转换为 Java
// The template bit here is just to pick the array dimensions from the array
// itself; you could also pass in a pointer and the dimensions.
template<std::size_t OuterDim, std::size_t InnerDim>
jobjectArray to_java(JNIEnv *env, int (&arr)[OuterDim][InnerDim]) {
// We allocate the int array first
jintArray inner = env->NewIntArray (InnerDim);
// to have an easy way to get its class when building the outer array
jobjectArray outer = env->NewObjectArray(OuterDim, env->GetObjectClass(inner), 0);
// Buffer to bring the integers in a format that JNI understands (jint
// instead of int). This step is unnecessary if jint is just a typedef for
// int, but on OP's platform this appears to not be the case.
std::vector<jint> buffer;
for(std::size_t i = 0; i < OuterDim; ++i) {
// Put the data into the buffer, converting them to jint in the process
buffer.assign(arr[i], arr[i] + InnerDim);
// then fill that array with data from the input
env->SetIntArrayRegion(inner, 0, InnerDim, &buffer[0]);
// and put it into the outer array
env->SetObjectArrayElement(outer, i, inner);
if(i + 1 != OuterDim) {
// if required, allocate a new inner array for the next iteration.
inner = env->NewIntArray(InnerDim);
}
}
return outer;
}
从Java到C++
// Note that this function does not return an array but a vector of vectors
// because 2-dimensional Java arrays need not be rectangular and have
// dynamic size. It is not generally practical to map them to C++ arrays.
std::vector<std::vector<int> > from_java(JNIEnv *env, jobjectArray arr) {
// always on the lookout for null pointers. Everything we get from Java
// can be null.
jsize OuterDim = arr ? env->GetArrayLength(arr) : 0;
std::vector<std::vector<int> > result(OuterDim);
for(jsize i = 0; i < OuterDim; ++i) {
jintArray inner = static_cast<jintArray>(env->GetObjectArrayElement(arr, i));
// again: null pointer check
if(inner) {
// Get the inner array length here. It needn't be the same for all
// inner arrays.
jsize InnerDim = env->GetArrayLength(inner);
result[i].resize(InnerDim);
jint *data = env->GetIntArrayElements(inner, 0);
std::copy(data, data + InnerDim, result[i].begin());
env->ReleaseIntArrayElements(inner, data, 0);
}
}
return result;
}
如您所见,它们非常简单;人们真的只需要知道调用什么 JNI 函数(或者,实际上,在哪里可以找到 the documentation)。
调用Java函数
至于调用 Java 函数,你几乎是正确的。有两件事需要改变:
mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");
如果 intArrayMethod
是 int intArrayMethod(int)
, 会起作用。事实上,你需要
mid = env->GetStaticMethodID(cls, "intArrayMethod", "([[I)[[I");
在这些签名串中,I
代表int
,[I
代表int[]
,[[I
代表int[][]
。一般来说,[type
代表type[]
。
这里:
cells = env->CallStaticIntMethod(cls, mid, cells);
两件事:
- 一开始就说了,
cells
不能原样传,因为Java不明白是什么;它需要先转换为 Java 数组(例如上面的转换函数),然后
intArrayMethod
不是 IntMethod;它不是 return 而是 int
而是一种复杂的数据类型。正确的调用必须使用 CallStaticObjectMethod
并将 jobject
转换为 return 实际有用的 JNI 类型。
正确的做法是:
jobjectArray java_cells = static_cast<jobjectArray>(env->CallStaticObjectMethod(cls, mid, to_java(env, cells)));
这为您提供了一个 jobjectArray
,您可以使用其他转换函数将其转换为向量的 C++ 向量:
std::vector<std::vector<int> > cpp_cells = from_java(env, java_cells);
然后可以像任何向量一样使用它。
我可以提及一些样式问题(例如在 Java 代码中依赖数组始终为 10x10,或者在有更好(阅读:类型安全)C++ 替代方案的地方使用或 C 函数 --我在看着你,printf
),但它们似乎不会触发此特定程序中的问题。
1 除非通过指针将其传回本机代码
如何使用 JNI 将二维数组从 C++ 传递到 Java 并从 return 传回 C++?
Sample2.java
public static int[][] intArrayMethod(int[][] n){
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
n[i][j] = 2;
}
}
return n;
}
CppSample.cpp
int main(){
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
jint cells[10][10]; // my 2d array
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
cells[i][j] = 1;
}
}
if (status != JNI_ERR){
cls = env->FindClass("Sample2");
if (cls != 0){
mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");
if (mid != 0){
cells = env->CallStaticIntMethod(cls, mid, cells);
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
printf("%d", cells[i][j]);
}
}
}
}
jvm->DestroyJavaVM();
return 0;
}
else{
return 1;
}
}
我不确定在 C++ 和 java 之间传递二维数组的正确方法是什么。
希望大家多多指导,谢谢!
这与其说是困难,不如说是乏味。普遍的问题是 Java 中的数组与 C++ 中的数组完全不同,并且(某种程度上)更类似于不可调整大小的向量。无法将 C++ 数组直接传递给 Java1,因为 Java 不知道如何处理它。所以我们需要转换函数,例如:
将二维数组从 C++ 转换为 Java
// The template bit here is just to pick the array dimensions from the array
// itself; you could also pass in a pointer and the dimensions.
template<std::size_t OuterDim, std::size_t InnerDim>
jobjectArray to_java(JNIEnv *env, int (&arr)[OuterDim][InnerDim]) {
// We allocate the int array first
jintArray inner = env->NewIntArray (InnerDim);
// to have an easy way to get its class when building the outer array
jobjectArray outer = env->NewObjectArray(OuterDim, env->GetObjectClass(inner), 0);
// Buffer to bring the integers in a format that JNI understands (jint
// instead of int). This step is unnecessary if jint is just a typedef for
// int, but on OP's platform this appears to not be the case.
std::vector<jint> buffer;
for(std::size_t i = 0; i < OuterDim; ++i) {
// Put the data into the buffer, converting them to jint in the process
buffer.assign(arr[i], arr[i] + InnerDim);
// then fill that array with data from the input
env->SetIntArrayRegion(inner, 0, InnerDim, &buffer[0]);
// and put it into the outer array
env->SetObjectArrayElement(outer, i, inner);
if(i + 1 != OuterDim) {
// if required, allocate a new inner array for the next iteration.
inner = env->NewIntArray(InnerDim);
}
}
return outer;
}
从Java到C++
// Note that this function does not return an array but a vector of vectors
// because 2-dimensional Java arrays need not be rectangular and have
// dynamic size. It is not generally practical to map them to C++ arrays.
std::vector<std::vector<int> > from_java(JNIEnv *env, jobjectArray arr) {
// always on the lookout for null pointers. Everything we get from Java
// can be null.
jsize OuterDim = arr ? env->GetArrayLength(arr) : 0;
std::vector<std::vector<int> > result(OuterDim);
for(jsize i = 0; i < OuterDim; ++i) {
jintArray inner = static_cast<jintArray>(env->GetObjectArrayElement(arr, i));
// again: null pointer check
if(inner) {
// Get the inner array length here. It needn't be the same for all
// inner arrays.
jsize InnerDim = env->GetArrayLength(inner);
result[i].resize(InnerDim);
jint *data = env->GetIntArrayElements(inner, 0);
std::copy(data, data + InnerDim, result[i].begin());
env->ReleaseIntArrayElements(inner, data, 0);
}
}
return result;
}
如您所见,它们非常简单;人们真的只需要知道调用什么 JNI 函数(或者,实际上,在哪里可以找到 the documentation)。
调用Java函数
至于调用 Java 函数,你几乎是正确的。有两件事需要改变:
mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");
如果 intArrayMethod
是 int intArrayMethod(int)
,会起作用。事实上,你需要
mid = env->GetStaticMethodID(cls, "intArrayMethod", "([[I)[[I");
在这些签名串中,I
代表int
,[I
代表int[]
,[[I
代表int[][]
。一般来说,[type
代表type[]
。
这里:
cells = env->CallStaticIntMethod(cls, mid, cells);
两件事:
- 一开始就说了,
cells
不能原样传,因为Java不明白是什么;它需要先转换为 Java 数组(例如上面的转换函数),然后 intArrayMethod
不是 IntMethod;它不是 return 而是int
而是一种复杂的数据类型。正确的调用必须使用CallStaticObjectMethod
并将jobject
转换为 return 实际有用的 JNI 类型。
正确的做法是:
jobjectArray java_cells = static_cast<jobjectArray>(env->CallStaticObjectMethod(cls, mid, to_java(env, cells)));
这为您提供了一个 jobjectArray
,您可以使用其他转换函数将其转换为向量的 C++ 向量:
std::vector<std::vector<int> > cpp_cells = from_java(env, java_cells);
然后可以像任何向量一样使用它。
我可以提及一些样式问题(例如在 Java 代码中依赖数组始终为 10x10,或者在有更好(阅读:类型安全)C++ 替代方案的地方使用或 C 函数 --我在看着你,printf
),但它们似乎不会触发此特定程序中的问题。
1 除非通过指针将其传回本机代码