NDK/JNI 开发之 Java 类和 C 结构体互转示例

字节流动

共 14306字,需浏览 29分钟

 · 2022-01-22

一、简介

JNI 开发中,常常会存在对应的 Java 类和 C 结构体需要互相转换。通过本实例学习和了解这个过程。

预备知识:

  1. JNI数据类型和类型描述符介绍:https://blog.csdn.net/afei__/article/details/80899758

  2. JNI 方法及使用示例:https://blog.csdn.net/afei__/article/details/81016413

二、目标

我们的目标就是通过 JNI 来完成下面两个类/结构体的互相转化。

1. Java 类

以下 DataBean 类基本包含了可能用得着的大部分属性(类、类数组、内部类、基本数据类型、基本数据类型数组、二维数组)了。

import android.graphics.PointF;
import android.graphics.Rect;
import java.util.Arrays;

public class DataBean {

public Rect mRect; // 其他类
public PointF[] mPoints; // 其它类数组
public Inner mInner; // 静态内部类

public int mID; // 整型
public float mScore; // 浮点型
public byte[] mData; // 基本类型数组
public int[][] mDoubleDimenArray; // 二维数组

public static class Inner {
public String mMessage; // 字符串
}
}

2. C 结构体

对应上面的 Java 类。

typedef struct jni_rect_t {
int left;
int top;
int right;
int bottom;
} jni_rect;

typedef struct jni_point_t {
float x;
float y;
} jni_point;

typedef struct jni_data_bean_t {
jni_rect rect; // Rect
jni_point points[4]; // PointF[]
const char *message; // String
int id; // int
float score; // float
signed char data[4]; // byte[]
int double_dimen_array[2][2]; // int[][]
} jni_data_bean;

三、开发

备注:本例开发环境为 Android Studio,NDK 编译方式为 Cmake。

1. NativeLibrary

首先新建一个类,来负责调用 native 方法。

public class NativeLibrary {
static {
System.loadLibrary("native-lib");
}
// 将C结构体转为Java类
public static native DataBean getDataFromNative();
// 将Java类转为C结构体
public static native void transferDataToNative(DataBean dataBean);
}

2. com_afei_jnidemo_NativeLibrary.cpp

对应 NativeLibrary 类中的 native 方法,这里使用的是 “静态注册” 的方式。

下面代码中,比较重要的地方,一个是在 JNI_OnLoad 方法中,我们调用了 register_classes 方法去注册类,这是因为我们要在 JNI 中使用 Java 的类、成员、方法,必须将他们先关联起来。

其次就是转换的两个方法的实现,也都放在了 Register.cpp 里了。

#include "com_afei_jnidemo_NativeLibrary.h"
#include "DataBean.h"
#include "Register.h"
#include "LogUtil.h"

void print(jni_data_bean *data_bean);

/**
* JNI 加载动态库的时候就会自动调用 JNI_OnLoad 方法
*/

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = JNI_ERR;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
}
register_classes(env); // 注册所有的类
return JNI_VERSION_1_6;
}

JNIEXPORT jobject JNICALL
Java_com_afei_jnidemo_NativeLibrary_getDataFromNative(JNIEnv *env, jclass type)
{
jni_data_bean data_bean = {
.rect = {0, 0, 640, 480},
.points = {
{0.0, 1.0},
{1.0, 2.0},
{2.0, 3.0},
{3.0, 4.0}
},
.message = "data from native",
.id = 0,
.score = 1.0,
.data = {0, 1, 2, 3},
.double_dimen_array = {
{0, 1},
{2, 3}
}
};
print(&data_bean);
jobject obj = data_bean_c_to_java(env, &data_bean);
return obj;
}

JNIEXPORT void JNICALL
Java_com_afei_jnidemo_NativeLibrary_transferDataToNative(JNIEnv *env, jclass type, jobject dataBean)
{
jni_data_bean data_bean;
data_bean_java_to_c(env, dataBean, &data_bean);
print(&data_bean);
}

3. Register.h

这里我们创建了一些新的结构体,方便存储各个类的相关信息。

#ifndef JNIDEMO_REGISTER_H
#define JNIDEMO_REGISTER_H

#include <jni.h>
#include "DataBean.h"

// 对应 android.graphics.Rect 类
typedef struct rect_block_t {
jclass clazz;
jfieldID left;
jfieldID top;
jfieldID right;
jfieldID bottom;
jmethodID constructor;
} rect_block;

// 对应 android.graphics.PointF 类
typedef struct point_block_t {
jclass clazz;
jfieldID x;
jfieldID y;
jmethodID constructor;
} point_block;

// 对应 com.afei.jnidemo.DataBean$Inner 类
typedef struct inner_block_t {
jclass clazz;
jfieldID message;
jmethodID constructor;
} inner_block;

// 对应 com.afei.jnidemo.DataBean 类
typedef struct data_bean_block_t {
jclass clazz;
jfieldID rect;
jfieldID points;
jfieldID inner;

jfieldID id;
jfieldID score;
jfieldID data;
jfieldID double_dimen_array;

jmethodID constructor;
} data_bean_block;

// 注册
void register_classes(JNIEnv *env);

// C结构体转Java类
jobject data_bean_c_to_java(JNIEnv *env, jni_data_bean *data_bean);

// Java类转C结构体
void data_bean_java_to_c(JNIEnv *env, jobject data_bean_in, jni_data_bean *data_bean_out);

#endif //JNIDEMO_REGISTER_H

4. Register.cpp

这里内容比较多,分几步来说:

a. 注册类信息

rect_block m_rect_block;
point_block m_point_block;
inner_block m_inner_block;
data_bean_block m_data_bean_block;

int find_class(JNIEnv *env, const char *name, jclass *clazz_out) {
jclass clazz = env->FindClass(name);
if (clazz == nullptr) {
LOGE("Can't find %s", name);
return -1;
}
*clazz_out = (jclass) env->NewGlobalRef(clazz); // 这里必须新建一个全局的引用
return 0;
}

int get_field(JNIEnv *env, jclass *clazz, const char *name, const char *sig, jfieldID *field_out) {
jfieldID filed = env->GetFieldID(*clazz, name, sig);
if (filed == nullptr) {
LOGE("Can't find. filed name: %s, sig: %s", name, sig);
return -1;
}
*field_out = filed;
return 0;
}

void register_rect_class(JNIEnv *env) {
int ret = find_class(env, "android/graphics/Rect", &m_rect_block.clazz);
if (ret != 0) {
LOGE("register_rect_class failed");
return;
}
jclass clazz = m_rect_block.clazz;
// 构造方法
m_rect_block.constructor = env->GetMethodID(clazz, "<init>", "()V");
// 成员
get_field(env, &clazz, "left", "I", &m_rect_block.left);
get_field(env, &clazz, "top", "I", &m_rect_block.top);
get_field(env, &clazz, "right", "I", &m_rect_block.right);
get_field(env, &clazz, "bottom", "I", &m_rect_block.bottom);
}

void register_point_class(JNIEnv *env) {
int ret = find_class(env, "android/graphics/PointF", &m_point_block.clazz);
if (ret != 0) {
LOGE("register_point_class failed");
return;
}
jclass clazz = m_point_block.clazz;
// 构造方法
m_point_block.constructor = env->GetMethodID(clazz, "<init>", "()V");
// 成员
get_field(env, &clazz, "x", "F", &m_point_block.x);
get_field(env, &clazz, "y", "F", &m_point_block.y);
}

void register_inner_class(JNIEnv *env) {
int ret = find_class(env, "com/afei/jnidemo/DataBean$Inner", &m_inner_block.clazz);
if (ret != 0) {
LOGE("register_inner_class failed");
return;
}
jclass clazz = m_inner_block.clazz;
// 构造方法
m_inner_block.constructor = env->GetMethodID(clazz, "<init>", "()V");
// 成员
get_field(env, &clazz, "mMessage", "Ljava/lang/String;", &m_inner_block.message);
}

void register_data_bean_class(JNIEnv *env) {
int ret = find_class(env, "com/afei/jnidemo/DataBean", &m_data_bean_block.clazz);
if (ret != 0) {
LOGE("register_data_bean_class failed");
return;
}
jclass clazz = m_data_bean_block.clazz;
// 构造方法
m_data_bean_block.constructor = env->GetMethodID(clazz, "<init>", "()V");
// 成员
get_field(env, &clazz, "mRect", "Landroid/graphics/Rect;", &m_data_bean_block.rect);
get_field(env, &clazz, "mPoints", "[Landroid/graphics/PointF;", &m_data_bean_block.points);
get_field(env, &clazz, "mInner", "Lcom/afei/jnidemo/DataBean$Inner;", &m_data_bean_block.inner);
get_field(env, &clazz, "mID", "I", &m_data_bean_block.id);
get_field(env, &clazz, "mScore", "F", &m_data_bean_block.score);
get_field(env, &clazz, "mData", "[B", &m_data_bean_block.data);
get_field(env, &clazz, "mDoubleDimenArray", "[[I", &m_data_bean_block.double_dimen_array);
}

void register_classes(JNIEnv *env) {
register_rect_class(env);
register_point_class(env);
register_inner_class(env);
register_data_bean_class(env);
}

实现头文件中的 register_classes 方法,完成四个相关 Java 类的注册逻辑。关于具体的 JNI 类型描述符和方法介绍,还请参考最上面 预备知识 提供的两个链接。

b. C转Java

jobject data_bean_c_to_java(JNIEnv *env, jni_data_bean *c_data_bean) {
if (c_data_bean == nullptr) {
LOGW("input data is null!");
return nullptr;
}
LOGD("start data_bean_c_to_java");

// 1. create rect
jobject rect = env->NewObject(m_rect_block.clazz, m_rect_block.constructor);
env->SetIntField(rect, m_rect_block.left, c_data_bean->rect.left);
env->SetIntField(rect, m_rect_block.top, c_data_bean->rect.top);
env->SetIntField(rect, m_rect_block.right, c_data_bean->rect.right);
env->SetIntField(rect, m_rect_block.bottom, c_data_bean->rect.bottom);

// 2. point array
jsize len = NELEM(c_data_bean->points);
LOGD("point array len: %d", len);
jobjectArray point_array = env->NewObjectArray(len, m_point_block.clazz, NULL);
for (int i = 0; i < len; i++) {
jobject point = env->NewObject(m_point_block.clazz, m_point_block.constructor);
env->SetFloatField(point, m_point_block.x, c_data_bean->points[i].x);
env->SetFloatField(point, m_point_block.y, c_data_bean->points[i].y);
env->SetObjectArrayElement(point_array, i, point);
}

// 3. inner class
jobject inner = env->NewObject(m_inner_block.clazz, m_inner_block.constructor);
jstring message = env->NewStringUTF(c_data_bean->message);
env->SetObjectField(inner, m_inner_block.message, message);

// 4. DataBean class
jobject java_data_bean = env->NewObject(m_data_bean_block.clazz, m_data_bean_block.constructor);
env->SetObjectField(java_data_bean, m_data_bean_block.rect, rect);
env->SetObjectField(java_data_bean, m_data_bean_block.points, point_array);
env->SetObjectField(java_data_bean, m_data_bean_block.inner, inner);
env->SetIntField(java_data_bean, m_data_bean_block.id, c_data_bean->id);
env->SetFloatField(java_data_bean, m_data_bean_block.score, c_data_bean->score);
// byte array
len = NELEM(c_data_bean->data);
LOGD("data array len: %d", len);
jbyteArray data = env->NewByteArray(len);
env->SetByteArrayRegion(data, 0, len, c_data_bean->data);
env->SetObjectField(java_data_bean, m_data_bean_block.data, data);
// double dimen int array
len = NELEM(c_data_bean->double_dimen_array);
LOGD("double dimen int array len: %d", len);
jclass clazz = env->FindClass("[I"); // 一维数组的类
jobjectArray double_dimen_array = env->NewObjectArray(len, clazz, NULL);
for (int i = 0; i < len; i++) {
jsize sub_len = NELEM(c_data_bean->double_dimen_array[i]);
LOGD("sub_len: %d", sub_len);
jintArray int_array = env->NewIntArray(sub_len);
env->SetIntArrayRegion(int_array, 0, sub_len, c_data_bean->double_dimen_array[i]);
env->SetObjectArrayElement(double_dimen_array, i, int_array);
}
env->SetObjectField(java_data_bean, m_data_bean_block.double_dimen_array, double_dimen_array);

return java_data_bean;
}

b. Java转C

void data_bean_java_to_c(JNIEnv *env, jobject data_bean_in, jni_data_bean *data_bean_out) {
if (data_bean_in == nullptr) {
LOGW("input data is null!");
return;
}
LOGD("start data_bean_java_to_c");

// 1. assign rect
jobject rect = env->GetObjectField(data_bean_in, m_data_bean_block.rect);
data_bean_out->rect.left = env->GetIntField(rect, m_rect_block.left);
data_bean_out->rect.top = env->GetIntField(rect, m_rect_block.top);
data_bean_out->rect.right = env->GetIntField(rect, m_rect_block.right);
data_bean_out->rect.bottom = env->GetIntField(rect, m_rect_block.bottom);

// 2. point array
jobjectArray point_array = (jobjectArray) env->GetObjectField(data_bean_in, m_data_bean_block.points);
jsize len = env->GetArrayLength(point_array);
// len = NELEM(data_bean_out->points);
LOGD("point array len: %d", len); // 注意这个 len 必须等于 NELEM(data_bean_out->points)
for (int i = 0; i < len; i++) {
jobject point = env->GetObjectArrayElement(point_array, i);
data_bean_out->points[i].x = env->GetFloatField(point, m_point_block.x);
data_bean_out->points[i].y = env->GetFloatField(point, m_point_block.y);
}

// 3. inner class
jobject inner = env->GetObjectField(data_bean_in, m_data_bean_block.inner);
jstring message = (jstring) env->GetObjectField(inner, m_inner_block.message);
data_bean_out->message = env->GetStringUTFChars(message, 0);

// 4. other
data_bean_out->id = env->GetIntField(data_bean_in, m_data_bean_block.id);
data_bean_out->score = env->GetFloatField(data_bean_in, m_data_bean_block.score);
// byte array
jbyteArray byte_array = (jbyteArray) env->GetObjectField(data_bean_in, m_data_bean_block.data);
jbyte *data = env->GetByteArrayElements(byte_array, 0);
len = env->GetArrayLength(byte_array);
LOGD("byte array len: %d", len);
memcpy(data_bean_out->data, data, len * sizeof(jbyte));
env->ReleaseByteArrayElements(byte_array, data, 0);
// double dimen int array
jobjectArray array = (jobjectArray) env->GetObjectField(data_bean_in, m_data_bean_block.double_dimen_array);
len = env->GetArrayLength(array); // 获取行数
LOGD("double dimen int array len: %d", len);
for (int i = 0; i < len; i++) {
jintArray sub_array = (jintArray) env->GetObjectArrayElement(array, i); // 这步得到的就是一维数组了
jint *int_array = env->GetIntArrayElements(sub_array, 0);
jsize sub_len = env->GetArrayLength(sub_array); // 获取列数
LOGD("sub_len: %d", sub_len);
memcpy(data_bean_out->double_dimen_array[i], int_array, sub_len * sizeof(jint));
env->ReleaseIntArrayElements(sub_array, int_array, 0);
}
LOGD("end data_bean_java_to_c");
}

5. 其它

以上其实大部分逻辑都完成了,其余没有列出来的代码和文件,可以在下面地址中找到:

https://github.com/afei-cn/JniDemo

这个工程可以直接在 Android 上运行和演示,并打印出相应信息。

四、相关链接

  1. JNI数据类型和类型描述符介绍:https://blog.csdn.net/afei__/article/details/80899758

  2. JNI 方法及使用示例:https://blog.csdn.net/afei__/article/details/81016413

  3. NDK 学习系列汇总篇:https://blog.csdn.net/afei__/article/details/81290711


来源:https://blog.csdn.net/afei__/article/details/95625394


-- END --


进技术交流群,扫码添加我的微信:Byte-Flow 



获取视频教程和源码



推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

NDK | 带你梳理 JNI 函数注册的方式和时机

Android NDK 开发:JNI 基础篇

Android NDK 开发:Java 与 Native 相互调用

Android NDK POSIX 多线程编程

NDK 开发中 Native 方法的静态注册与动态注册

Android NDK 开发中快速定位 Crash 问题

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报