NDK/JNI 開發之 Java 類和 C 結構體互轉示例

2022-01-28 字節流動
一、簡介

JNI 開發中,常常會存在對應的 Java 類和 C 結構體需要互相轉換。通過本實例學習和了解這個過程。

預備知識:

JNI數據類型和類型描述符介紹:https://blog.csdn.net/afei__/article/details/80899758

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 上運行和演示,並列印出相應信息。

四、相關連結

JNI數據類型和類型描述符介紹:https://blog.csdn.net/afei__/article/details/80899758

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

NDK 學習系列匯總篇:https://blog.csdn.net/afei__/article/details/81290711

相關焦點

  • 音視頻開發之旅(17) JNI與NDK的學習和使用
    二、Java和Native交互流程JNI在Java類中通過native關鍵字聲明Native方法javac命令編譯Java類得到class文件通過javah命令(javah -jni class名稱)導出JNI的頭文件(.h文件)實現native方法編譯生成動態庫(.so文件)實現Java和C、CPP的相互調用
  • NDK開發_編譯的cpp引用到 其它so, Android.mk 的寫法
    一、不需要ndk編譯 .cpp,直接是 so文件切到 Project 視圖,在java同級目錄下 新建 jniLibs文件夾,再根據平臺需要,在jniLibs目錄下建 armeabi-v7/x86 ... 文件夾,下面的截圖 根據平臺需要建的是armeabi-v7a文件夾,然後將相應的 so文件複製到該目錄下即可。
  • NDK開發之初始化函數
    NDK開發中有一些特殊函數,分別用來初始化動態庫和關閉時做一些必要的處理,開發時可把認為需要的代碼放到這兩個函數裡面, 它們分別在動態庫被加載和釋放的時候被執行。常用的初始化函數有三個,本文將一一舉例。
  • Java築基 - JNI到底是個啥
    首先回顧一下jni的主要功能,從jdk1.1開始jni標準就成為了java平臺的一部分,它提供的一系列的API允許java和其他語言進行交互,實現了在java代碼中調用其他語言的函數。1、準備java代碼首先定義一個包含了native方法的類如下,之後我們要使用這個類中的native方法通過jni調用c++編寫成的動態連結庫中的方法:public class JniTest {    static{        System.loadLibrary
  • java基礎之理解JNI原理
    有了JAVA標準平臺的支持,使JNI模式更加易於實現和使用。在此總結了下面這個知識圖:實例:環境說明:ubuntu 10.4.2 LTS系統程序清單1:src/com/magc/jni/HelloWorld.java/** *  */ package com.magc.jni; /** * @author magc
  • struct2json V1.0 發布,C 結構體與 JSON 互轉庫
    struct2json 是一個開源的C結構體與 JSON 快速互轉庫,它可以快速實現 結構體對象 與 JSON 對象 之間序列化及反序列化要求
  • Java通過-jni調用c語言
    文件生成TestJNI.class文件(3)用javah帶-jni參數編譯TestJNI.class文件生成TestJNI.h文件。該文件中定義了c的函數原型。在實現c函數的時候需要。參數中, 我們也只需要關心在Java程序中存在的參數,至於JNIEnv*和jclass我們一般沒有必要去碰它。
  • Android NDK 入門與實踐之 CMake
    註:如果您的項目使用 ndk-build,則不需要創建 CMake 構建腳本。提供一個指向您的 Android.mk 文件的路徑,將 Gradle 關聯到您的原生庫。1、從 IDE 的左側打開 Project 窗格並從下拉菜單中選擇 Project 視圖;2、右鍵點擊 您的模塊 的根目錄並選擇 New > File;3、輸入「CMakeLists.txt」作為文件名並點擊 OK;CMakeLists.txt 示例:# 設置構建本地庫所需的最小版本cmake_minimum_required
  • JNI-Thread中start方法調用與run方法回調分析
    我們查看該.h文件,其中就包含了jniHello方法的定義,當然需要注意到的是,這裡的方法名和.h文件本身的命名是jni根據我們類的包名和類名確定出來的,不能修改。(JNIEnv *env, jobject c1){ printf("hello jni\n");}在該文件中,引入了之前生成.h文件(類似於java指定了類實現了哪個接口),並且定義了籤名完全一致的Java_cn_tera_jni_JniTest_jniHello方法,此時我們已經有了「接口」和「實現」,接著生成動態連結庫即可。
  • 摘要:編寫第一個NDK,使用C語言進行輸出。
    ,填寫生成庫的名稱,會在項目目錄下生成jni文件夾和obj文件夾。編寫一個java類NativeUtils,其中包含多個native修飾的方法。生成native方法所對應的.h頭文件。到項目名稱/bin/class文件夾下執行javah命令,會在class目錄下生成.h文件,然後將該頭文件從class目錄拷貝到jni文件夾下解決could not resolved的方法:右鍵項目->Properties->C/C++ General ->Paths and Symbols ->Add...
  • 實現一個在JNI中調用Java對象的工具類,從此一行代碼就搞定!
    前言我們知道在jni中執行一個java函數需要調用幾行代碼才行,如jclass objClass = (*env).GetObjectClass
  • Swift 中的類與結構體
    本文結合源碼探究類和結構體的本質。類和結構體的異同Swift中,類和結構體有許多相似之處,但也有不同。我們都知道,內存分配可以分為堆區(Heap)和棧區(Stack)。類(class)和結構體(struct)在內存分配上是不同的,基本數據類型和結構體默認分配在棧區,而像類這種高級數據類型存儲在堆區,且堆區數據存儲不是線程安全的,在頻繁的數據讀寫操作時,要進行加鎖操作。
  • JVM 解剖公園:JNI 臨界區與 GC Locker
    因此,這裡的數據和討論可以當軼事看,不做寫作風格、句法和語義錯誤、重複或一致性檢查。如果選擇採信文中內容,風險自負。相反,在 `acquire` 和 `release` 方法之間申請並保存大量對象:```javapublic class CriticalGC { static final int ITERS = Integer.getInteger("iters", 100); static final int ARR_SIZE = Integer.getInteger
  • 研究了一下Android JNI,有幾個知識點不太懂.
    static jobject g_class_loader = NULL;static jmethodID g_find_class_method = NULL;void on_load() {    JNIEnv *env = get_jni_env(); if (!
  • 迅為開發板安卓JNI開發實戰教程使用編譯好的 so 庫
    轉自迅為開發板安卓JNI開發指南硬體平臺:迅為iTOP-412開發板B站搜索:北京迅為,免費觀看同步視頻教程:上一小節我們已經編譯了 so 庫,那麼我們或者其他人拿到這個 so 後要怎麼使用呢我們打開 AS,然後新建一個項目,選擇一個空的 Activity,如下圖所示:這裡我要注意一下,我們的包名要和我們調用的 jni 庫的包名一樣,否則會出問題,之前我們在寫 jni 的時候,我們的包名如下圖所示:包名:那麼我們在新建工程的時候包名也要是這個
  • C語言中的結構體和聯合體
    由於結構體將一組相關變量看作一個單元而不是各自獨立的實體,因此結構體有助於組織複雜的數據,特別是在大型的程序中。共用體(union),也稱為聯合體,是用於(在不同時刻)保存不同類型和長度的變量,它提供了一種方式,以在單塊存儲區中管理不同類型的數據。今天,我們來介紹一下 C 語言中結構體和共用體的相關概念和使用。
  • Android NDK Crash 定位分析
    當拿到應用的crash日誌,如果是在java層出現了異常,相信大家都知道通過堆棧信息查找到奔潰的代碼,但是如果是在native層出現了問題,面對下面的一堆內存地址
  • C語言編程 — 結構體與位域
    我們可以使用結構體表示更加複雜的數據類型。注意:我們應該將結構體定義在所有需要用到它的函數的上方,枚舉類型和基本數據類型的使用方法沒有任何區別。可見,結構體就像一個 「模板」,定義出來的變量都具有相同的性質,可以使用結構體來實現 C++ 中的類和實例的繼承機制。結構體的定義具有多種方式,比較靈活。
  • Android JNI 之 Bitmap 操作
    在 Android 通過 JNI 去調用 Bitmap,通過 CMake 去編 so 動態連結庫的話,需要添加 jnigraphics 圖像庫。1target_link_libraries( # Specifies the target library.