JNI如何将Java对象数组传递到C++同类型对象数组
解决JNI中Java对象数组转C++类对象的问题
嘿,这个问题我之前做JNI开发的时候也踩过坑!咱们先搞清楚为啥报错,再一步步解决~
首先得明确:绝对不能直接把jobject强制转换成C++的Foo类型,也不能直接赋值。原因很简单:
- Java里的
Foo对象是在JVM堆上创建的,而C的Foo类是Native层的对象,两者属于完全独立的内存空间,JNI里的jobject只是JVM给Native层的一个"引用令牌",不是C对象本身,所以C++根本认不出这个类型。
那该怎么处理呢?分两种常见场景:
场景1:Java Foo和C++ Foo是数据结构对应(无Native绑定)
如果Java的Foo只是普通数据类,和C的Foo有相同的成员变量(比如都有id、name),那你需要通过JNI API读取Java对象的字段(或者调用getter方法),手动把数据复制到C的Foo对象里。
举个具体例子:
假设Java的Foo类是这样的:
public class Foo { private int id; private String name; public Foo(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }
对应的C++ Foo类:
#include <cstring> struct Foo { int id; char name[256]; // 假设用固定长度数组存字符串 };
那你的JNI代码应该改成这样:
#include "headers/Foo.h" #include <vector> #include <cstring> extern "C" JNIEXPORT void JNICALL Java_main_Main_Interface(JNIEnv *env, jobject obj, jobjectArray fooArray){ jint size = env->GetArrayLength(fooArray); if (size == 0) return; // 1. 获取Java Foo类的Class引用和方法ID jobject firstFoo = env->GetObjectArrayElement(fooArray, 0); jclass fooCls = env->GetObjectClass(firstFoo); // 获取getId()方法的ID:签名()I表示无参返回int jmethodID getIdMethod = env->GetMethodID(fooCls, "getId", "()I"); // 获取getName()方法的ID:签名()Ljava/lang/String;表示无参返回String jmethodID getNameMethod = env->GetMethodID(fooCls, "getName", "()Ljava/lang/String;"); // 2. 遍历数组,逐个转换为C++ Foo对象 // 这里用std::vector代替栈上数组,避免C++标准不支持变长栈数组的问题 std::vector<Foo> cfooList; cfooList.reserve(size); for(int i = 0; i < size; i++){ jobject javaFoo = env->GetObjectArrayElement(fooArray, i); // 调用getter获取Java对象的成员值 jint id = env->CallIntMethod(javaFoo, getIdMethod); jstring nameStr = static_cast<jstring>(env->CallObjectMethod(javaFoo, getNameMethod)); const char* nameUtf = env->GetStringUTFChars(nameStr, nullptr); // 构造C++ Foo对象 Foo cfoo; cfoo.id = id; // 复制字符串,注意截断避免越界 std::strncpy(cfoo.name, nameUtf, sizeof(cfoo.name) - 1); cfoo.name[sizeof(cfoo.name) - 1] = '\0'; // 确保字符串以'\0'结尾 cfooList.push_back(cfoo); // 3. 释放JNI资源,避免内存泄漏和局部引用溢出 env->ReleaseStringUTFChars(nameStr, nameUtf); env->DeleteLocalRef(javaFoo); } // 释放剩余的局部引用 env->DeleteLocalRef(firstFoo); env->DeleteLocalRef(fooCls); // 现在你可以使用cfooList里的C++ Foo对象了 }
这里要注意几个细节:
- 不要用
Foo cfoo[size]这种栈上变长数组,C++标准不支持,建议用std::vector或者new分配堆内存。 - 用完JNI的局部引用(比如
javaFoo、firstFoo)一定要调用DeleteLocalRef,不然会占满JVM的局部引用表导致崩溃。 - 字符串处理完必须调用
ReleaseStringUTFChars,释放JVM分配的字符串内存。
场景2:Java Foo是绑定Native实现的类
如果你的Java Foo类本身就对应一个Native层的Foo对象(比如Java类里有native方法,需要调用C++的Foo逻辑),那可以让Java类持有一个Native对象的指针,在Native层取出这个指针直接使用。
步骤如下:
- 修改Java Foo类,添加一个存储Native指针的字段:
public class Foo { // 存储C++ Foo对象的指针,用long类型(因为指针在64位系统是8字节,long刚好能存) private long nativePtr; public Foo() { // 调用native方法创建C++ Foo对象,并把指针存下来 nativePtr = createNativeFoo(); } // 创建Native Foo的native方法 private native long createNativeFoo(); // 销毁Native Foo的native方法,避免内存泄漏 public native void destroyNativeFoo(); // 重写finalize,确保对象被回收时销毁Native资源 @Override protected void finalize() throws Throwable { destroyNativeFoo(); super.finalize(); } }
- 编写对应的Native代码:
#include "headers/Foo.h" // 创建C++ Foo对象,返回指针 extern "C" JNIEXPORT jlong JNICALL Java_com_example_Foo_createNativeFoo(JNIEnv* env, jobject obj) { Foo* foo = new Foo(); // 把指针转换成jlong返回给Java return reinterpret_cast<jlong>(foo); } // 销毁C++ Foo对象 extern "C" JNIEXPORT void JNICALL Java_com_example_Foo_destroyNativeFoo(JNIEnv* env, jobject obj) { jclass cls = env->GetObjectClass(obj); // 获取nativePtr字段的ID,签名J表示long类型 jfieldID ptrField = env->GetFieldID(cls, "nativePtr", "J"); jlong ptrVal = env->GetLongField(obj, ptrField); if (ptrVal != 0) { Foo* foo = reinterpret_cast<Foo*>(ptrVal); delete foo; // 把指针置0,避免重复销毁 env->SetLongField(obj, ptrField, 0); } env->DeleteLocalRef(cls); } // 处理Java Foo数组的JNI方法 extern "C" JNIEXPORT void JNICALL Java_main_Main_Interface(JNIEnv *env, jobject obj, jobjectArray fooArray){ jint size = env->GetArrayLength(fooArray); if (size == 0) return; // 获取Java Foo类的Class引用和nativePtr字段ID jclass fooCls = env->FindClass("com/example/Foo"); jfieldID ptrField = env->GetFieldID(fooCls, "nativePtr", "J"); std::vector<Foo*> cfooPtrs; cfooPtrs.reserve(size); for(int i = 0; i < size; i++){ jobject javaFoo = env->GetObjectArrayElement(fooArray, i); // 取出nativePtr的值,转成C++ Foo指针 jlong ptrVal = env->GetLongField(javaFoo, ptrField); Foo* cfoo = reinterpret_cast<Foo*>(ptrVal); cfooPtrs.push_back(cfoo); env->DeleteLocalRef(javaFoo); } // 现在你可以直接使用cfooPtrs里的C++ Foo指针操作Native对象了 env->DeleteLocalRef(fooCls); }
这种方式的好处是不需要复制数据,直接操作Native层的对象,效率更高,但需要维护Native对象的生命周期,避免内存泄漏。
内容的提问来源于stack exchange,提问作者bcsta
相关产品推荐
相关产品推荐

