前段时间写过一篇 来编译一个自己的 so 库吧,对于 jni 命名等知识点有一定的涉及,这里就不重复说了,本节的重点放在介绍 jni 的数据传递上,即 java 如何传递对象给 c++,而 c++ 又如何将数据封装成 java 所需的对象。
java 结构体传到 c++ 侧:
对象
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
boolean isT;
}
直入主题,对于基本数据类型的属性,很方便:
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass jc_Test = env->GetObjectClass(object); /* object是native方法内,传递的Test */
jfieldID jf_isT = env->GetFieldID(jc_Test, "isT", "Z"); /* argument #2:属性名称,argument #3:对应的jni类型 */
Extra extra_; /* 创建c++对象 Extra */
extra_.param = env->GetBooleanField(object, jf_isT); /* 获取java 对象的isT值,并赋值给c++对象param属性 */
return 0;
}
其他基本类型使用方式类似,唯一需要注意的是long 和 boolean,对应 J 和 Z。数组 类型同理,对应 jbyteArray 和 [B
String 类型
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
String str;
}
对于String属性,可以这样:
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass jc_Test = env->GetObjectClass(object);
jfieldID jf_str = env->GetFieldID(jc_Test, "str", "Ljava/lang/String;");
auto jstr = (jstring) env->GetObjectField(object, jf_str);
Extra extra_;
extra_.param = = common::String::getString(env, jstr); /* jstring需要转换成c++需要的string */
env->DeleteLocalRef(jstr); /* 记得销毁 */
return 0;
}
这是工具类的头文件:
#include <jni.h>
#include <string>
namespace common {
class String {
public:
static std::string getString(JNIEnv *env, jstring jStr);
static jstring getJString(JNIEnv *env, const char *str);
};
}
关于jstring 和 std::string的转换,不像 java 那么方便,代码见下:
static const char *NAME = "java/lang/String";
jclass cls = env->FindClass(NAME);
jmethodID String::methodGetBytes = env->GetMethodID(clazz, "getBytes", "(Ljava/lang/String;)[B");
std::string String::getString(JNIEnv *env, jstring jStr) {
if (jStr == nullptr)
return "";
jstring encoding = env->NewStringUTF("UTF-8");
const auto stringJBytes = (jbyteArray) env->CallObjectMethod(jStr, methodGetBytes, encoding);
auto length = (size_t) env->GetArrayLength(stringJBytes);
jbyte* pBytes = env->GetByteArrayElements(stringJBytes, nullptr);
std::string ret = std::string((char *)pBytes, length);
env->ReleaseByteArrayElements(stringJBytes, pBytes, JNI_ABORT);
env->DeleteLocalRef(stringJBytes);
env->DeleteLocalRef(encoding);
return ret;
}
std::string 转换 jstring 见下:
jstring j_str = common::String::getJString(env, str_.c_str()); /* str_ 为std::string 类型,j_str 为jstring 类型 */
static const char *NAME = "java/lang/String";
jclass cls = env->FindClass(NAME);
jmethodID String::methodConstructor = env->GetMethodID(clazz, "<init>", "([BLjava/lang/String;)V");
jstring String::getJString(JNIEnv *env, const char *str) {
if (str == nullptr) {
return nullptr;
}
int length = strlen(str);
jbyteArray jBytes = env->NewByteArray(length);
env->SetByteArrayRegion(jBytes, 0, length, (jbyte *) str);
jstring encoding = env->NewStringUTF("UTF-8");
auto result = (jstring) env->NewObject(clazz, methodConstructor, jBytes, encoding);
env->DeleteLocalRef(jBytes);
env->DeleteLocalRef(encoding);
return result;
}
对象嵌套对象
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
Delegate d; /* 全限定名 com.test.Delegate */
}
class Delegate {
float i;
}
对于嵌套类,可以这样:
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass jc_Test = env->GetObjectClass(object); /* 先获取 Test.class */
jfieldID jf_delegate = env->GetFieldID(jc_Test, "d", "Lcom/test/Delegate;");
jobject jDelegate = env->GetObjectField(object, jf_delegate);
if (jDelegate != nullptr) {
jclass clsDelegate = env->GetObjectClass(jDelegate); /* 再获取 Delegate.class */
jfieldID jf_del_i = env->GetFieldID(clsDelegate, "i", "F");
Extra extra_; /* 将float 值赋给c++*/
extra_.param = env->GetFloatField(jDelegate, jf_del_i);
}
env->DeleteLocalRef(jDelegate);
return 0;
}
List 对象列表
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
ArrayList<Delegate> list;
}
class Delegate { /* 全限定名 com.test.Delegate */
float i;
}
对于容器类,我们可以这样:
static const char* NAME_ArrayList = "java/util/ArrayList";
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
jclass jc_Test = env->GetObjectClass(object);
jfieldID jf_delegates = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
jobject jDelegates = env->GetObjectField(object, jf_delegates); /* 获取list */
if (jDelegates != nullptr) {
jint size = env->CallIntMethod(jDelegates, methodSize);
for (int i = 0; i < size; i++) {
jobject jDelegate = env->CallObjectMethod(jDelegates, methodGet, i); /* 拿到list item元素 */
jclass cls_delegate = env->GetObjectClass(jDelegate);
jfieldID jf_delegate = env->GetFieldID(cls_delegate, "i", "F"); /* 拿到每个元素内的属性值 */
Extra extra_; /* 将float 值赋给c++ */
extra_.param = env->GetFloatField(jDelegate, jf_delegate);
env->DeleteLocalRef(jDelegate);
}
}
env->DeleteLocalRef(jDelegates);
return 0;
}
List 基本数据列表
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
ArrayList<Integer> list;
}
对于List 内有基本类型的包装类,我们可以这样:
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
jclass jc_Test = env->GetObjectClass(object);
jfieldID jf_indexs = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
jobject jIndexs = env->GetObjectField(object, jf_indexs); /* 获取list */
if (jIndexs != nullptr) {
jint size = env->CallIntMethod(jIndexs, methodSize);
for (int i = 0; i < size; i++) {
jint jIndex = (jint) env->CallObjectMethod(jIndexs, methodGet, i); /* 拿到list item元素 */
Extra extra_; /* 将 int 值赋给c++ */
extra_.param = jIndex;
}
}
env->DeleteLocalRef(jIndexs);
return 0;
}
List 嵌套 List
写一个简单的类,需要从 java 层传递给 c++:
package com.test;
class Test {
ArrayList<ArrayList<Delegate>> list;
}
class Delegate { /* 全限定名 com.test.Delegate */
float i;
}
对于List 巧套 List,跟上面单纯的 List 一样,不过就是一层一层解析罢了,代码见下:
static const char* NAME_ArrayList = "java/util/ArrayList";
JNIEXPORT jint JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass clsArrayList = env->FindClass(NAME_ArrayList); /* 注意List 和 ArrayList 有区别 */
jmethodID methodGet = env->GetMethodID(clsArrayList, "get", "(I)Ljava/lang/Object;");
jmethodID methodSize = env->GetMethodID(clsArrayList, "size", "()I");
jclass jc_Test = env->GetObjectClass(object);
jfieldID jf_delegates = env->GetFieldID(jc_Test, "list", "Ljava/util/ArrayList;");
jobject jDelegates = env->GetObjectField(object, jf_delegates); /* 获取外层list */
if (jDelegates != nullptr) {
jint size = env->CallIntMethod(jDelegates, methodSize);
for (int i = 0; i < size; i++) {
jobject jDelegate = env->CallObjectMethod(jDelegates, methodGet, i);
jint s = env->CallIntMethod(jDelegate, methodSize);
for (int j = 0; j < s; j++) {
jobject j_inner_delegate = env->CallObjectMethod(jDelegate, methodGet, j); /* 获取内层 list*/
jclass cls_inner_delegate = env->GetObjectClass(j_inner_delegate);
jfieldID jf_inner_deledate = env->GetFieldID(cls_inner_delegate, "i", "F");
jfloat i = env->GetFloatField(j_inner_delegate, jf_inner_deledate) /* 目标值 */
env->DeleteLocalRef(j_inner_delegate);
}
env->DeleteLocalRef(jDelegate);
}
}
env->DeleteLocalRef(jDelegates);
return 0;
}
c++ 数据吐给 java 结构体
对象
写一个简单的类,需要从 c++ 中取得数据并赋值给该结构体,提供给 java 侧使用:
package com.test;
class Test {
float i;
}
其实与上面讲的非常类似,代码见下:
static const char* NAME_Test = "com/test/Test";
JNIEXPORT jobject JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass cls_test = env->FindClass(NAME_Test);
jmethodID methodInit = env->GetMethodID(cls_test, "<init>", "()V"); /* 无参构造 */
jobject j_test = env->NewObjectA(cls_test, methodInit, 0); /* java 结构体 Test 对象 */
jfieldID jf_test = env->GetFieldID(cls_test, "i", "F");
env->SetIntField(j_test, jf_test, 1.0f); /* 给结构体属性赋值 */
return j_test;
}
List 列表
写一个简单的类,需要从 c++ 中取得数据并赋值给该结构体,提供给 java 侧使用:
package com.test;
class Test {
ArrayList<Delegate> list;
}
class Delegate { /* 全限定名 com.test.Delegate */
float i;
}
这是一个容器类,其实也差不多:
static const char* NAME_Test = "com/test/Test";
static const char* NAME_Delegate = "com/test/Delegate";
static const char* NAME_ArrayList = "java/util/ArrayList";
JNIEXPORT jobject JNICALL Java_com_test_NativeApi_m
(JNIEnv *, jobject thiz, jobject object) {
jclass cls_test = env->FindClass(NAME_Test);
jmethodID methodInit = env->GetMethodID(cls_test, "<init>", "()V"); /* 无参构造 */
jobject j_test = env->NewObjectA(cls_test, methodInit, 0); /* java 结构体 Test 对象 */
jclass clsArrayList = env->FindClass(NAME_ArrayList);
jmethodID methodAdd = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");
jfieldID jf_delegates = env->GetFieldID(cls_test, "list", "Ljava/util/ArrayList;");
jmethodID methodInit_list = env->GetMethodID(clsArrayList, "<init>", "()V");
jobject j_delegates = env->NewObject(clsArrayList, methodInit_list, "");
env->SetObjectField(j_test, jf_delegates, j_delegates);
for (auto iter : tests_) { /* test_ 是c++ 定义的容器,这里就不写了 */
jclass cls_delegate = env->FindClass(NAME_Delegate);
jmethodID methodInit_del = env->GetMethodID(cls_delegate, "<init>", "()V");
jobject j_delegate = env->NewObject(cls_delegate, methodInit_del, ""); /* 创建内部的 Delegate 对象 */
env->CallBooleanMethod(j_delegates, methodAdd, j_delegate); /* 添加到list容器内 */
jfieldID jf_del = env->GetFieldID(cls_delegate, "i", "F");
env->SetIntField(j_delegate, jf_del, 1.0f); /* 给结构体属性赋值 */
env->DeleteLocalRef(j_delegate);
}
env->DeleteLocalRef(j_delegates);
return j_test;
}
其他的结构体就不介绍了,其实来来回回就这点东西,有一点需要注意的:
package com.test;
class Test {
ArrayList<Integer> list;
}
当List 内为包装类时,我们也需要这样创建包装类:
jclass cls_integer = env->FindClass("java/lang/Integer");
jmethodID methodInteger = env->GetMethodID(cls_integer, "<init>", "(I)V");
数组
vector< char> 转成 byte[] ,可以这样:
std::vector<char> rawData = c_rsp->GetRawdata(); /* c++ 方法,可以不管 */
jfieldID jfRawDataID = env->GetFieldID(cls_batchRsp, "rawData", "[B");
int size = rawData.size();
jbyteArray j_raw_array = env->NewByteArray(size); /* 反给java 的结构体 byte[] */
jbyte *j = new jbyte[size];
std::memcpy(j, &rawData[0], size);
env->SetByteArrayRegion(j_raw_array, 0, size, j);
env->SetObjectField(j_batchRsp, jfRawDataID, j_raw_array);
byte[] 转成 char* data,可以直接这样:
char *c_array = (char*) env->GetByteArrayElements(array, 0);
int len_arr = env->GetArrayLength(array);
尾声
关于 jni 的结构体的传递,就介绍到这里了,实际开发里,肯定还会遇到各类问题,到时候多多google 就好了。