JNI层与Java层结构体传递
最近在做移动终端开发,需要把native层C++一些统计数据传递给java层使用,在做这部分功能的时候发现JNI层与Java层结构体以及内嵌结构体传递在网上的资料甚少,因此完成功能后做一下这方面的总结,方面后人学习。
- JNI函数返回一个结构体
- 首先我们定义C层的结构体和函数
typedef struct _VoiceAPIStat { int nSLESRecOpenSucceedCnt; //open slel mic 创建成功次数 int nSLESRecOpenFailedCnt; //open slel mic 创建失败次数 int nSLESPlayOpenSucceedCnt; //open slel render 创建成功次数 int nSLESPlayOpenFailedCnt; //open slel render 创建失败次数 int nJAVARecOpenSucceedCnt; //java mic 创建成功次数 int nJAVARecOpenFailedCnt; //java mic 创建失败次数 int nJAVAPlayOpenSucceedCnt; //java render 创建成功次数 int nJAVAPlayOpenFailedCnt; //java render 创建失败次数 }VoiceAPIStat; // ------------------------------------------------------------------------------------------------- // 系统关键指标数据统计 // ------------------------------------------------------------------------------------------------- typedef struct _EngRunInfoStat { bool bStatisticsEnable; <span style="white-space:pre"> </span>// 统计是否使能(为假,则不上报) int nStatisticsLevel; // 统计级别(为0,则不统计) int nRptIntervalInMs; // 上报周期(毫秒) int nSpeakMode; //说话模式 int nJavaAPILevel; //java api 级别 VoiceAPIStat stVoiceApiStat; //音频采集API信息统计 }EngRunInfoStat;
//定义C层获取结构体信息函数
如代码所示,我们定义一个嵌套的结构体,定义C层提供给JNI层获取EngRunInfoStat结构体参数信息的函数,EngRunInfoStat是一个嵌套结构体,我们最终目的把该C层该结构体值传递到Java层。int GetRunInfoStat(EngRunInfoStat *pAllStat);
- 定义Java层对应的函数和结构体类
package com.test.eng; public class EngRunInfoStat { public boolean bStatisticsEnable; // 统计是否使能(为假,则不上报) public int nStatisticsLevel; // 统计级别(为0,则不统计) public int nRptIntervalInMs; // 上报周期(毫秒) public int nSpeakMode; //说话模式 public int nJavaAPILevel; //java api 级别 public VoiceAPIStat stVoiceApiStat; //音频采集API信息统计 public class VoiceAPIStat { public int nSLESRecOpenSucceedCnt; //open slel mic 创建成功次数 public int nSLESRecOpenFailedCnt; //open slel mic 创建失败次数 public int nSLESPlayOpenSucceedCnt; //open slel render 创建成功次数 public int nSLESPlayOpenFailedCnt; //open slel render 创建失败次数 public int nJAVARecOpenSucceedCnt; //java mic 创建成功次数 public int nJAVARecOpenFailedCnt; //java mic 创建失败次数 public int nJAVAPlayOpenSucceedCnt; //java render 创建成功次数 public int nJAVAPlayOpenFailedCnt; //java render 创建失败次数 }; };
如java代码定义对应C层EngRunInfoStat结构体对应的EngRunInfoStat java类,并且定义与jni层通信的native函数,native函数如果与JNI使用不是本节重点,不做额外描述,重点放在结构体传递上。//定义对应获取类函数 public final static native EngRunInfoStat GetRunInfoStat();
- 通过JNI层实现C层与Java层结构体转换 JNI层的代码实现是最关键的,首先我们要获取对应Java层定义的EngRunInfoStat类,并且需要获取该类的构造函数,然后通过NewObject函数创建该类的一个实例,注意我们EngRunInfoStat类里面还包含一个内嵌类,通过NewObject创建EngRunInfoStat类并没有包含内嵌类的实例,内嵌类我们后面还需单独的创建,先看示例代码
objectNewEng即是我们新创建出来EngRunInfoStat实例,为了给类成员变量赋值,我们还需要将类中成员变量ID导出来,用于后续赋值操作//获取Java实例 jclass objectClass = (env)->FindClass("com/test/eng/EngRunInfoStat"); //获取成员变量 jmethodID objectClassInitID = (env)->GetMethodID(objectClass, "<init>", "()V"); jobject objectNewEng = (env)->NewObject(objectClass, objectClassInitID);
如代码所示,普通成员变量jfiledID通过GetFieldID(),根据变量名称和类型签名导出即可,关键是成员中内嵌类导出比较特殊,stVoiceApiStat是一个内嵌类成员变量它是VoiceAPIStat内嵌类,内嵌类的签名比较特殊,首先是L开头代表类,然后包名,外部类与内部类签名之间用$隔开,即“Lcom/test/eng/EngRunInfoStat$VoiceAPIStat;”,获取到成员变量字段ID后,对于一般成员变量,我们就可以对其赋值了,示例如下:jfieldID bStatisticsEnable = (env)->GetFieldID(objectClass, "bStatisticsEnable", "Z"); jfieldID nStatisticsLevel = (env)->GetFieldID(objectClass, "nStatisticsLevel", "I"); jfieldID nRptIntervalInMs = (env)->GetFieldID(objectClass, "nRptIntervalInMs", "I"); jfieldID nSpeakMode = (env)->GetFieldID(objectClass, "nSpeakMode", "I"); jfieldID nJavaAPILevel = (env)->GetFieldID(objectClass, "nJavaAPILevel", "I"); jfieldID stVoiceApiStat = (env)->GetFieldID(objectClass, "stVoiceApiStat", "Lcom/test/eng/EngRunInfoStat$VoiceAPIStat;");
GetRunInfoStat是C层获取C结构体变量的函数,stEngInfoStat是获取数据后保存的变量,获取数据成功后就可以将每个值设置到对应java类成员变量中,即通过SetIntField()函数设置,用法见示例代码,可以看到我们还没有对内嵌类stVoiceApiStat进行操作,这是因为对内嵌类的操作比较特殊,与普通成员变量需要区别对待,对于内嵌类我们首先要导出内嵌类对象,然后到处内嵌类构造函数,接着用NewObject创建内嵌类的实例,示例代码如下:if(0 == GetRunInfoStat(&stEngInfoStat)) { (env)->SetBooleanField(objectNewEng, bStatisticsEnable, (jboolean)stEngInfoStat.bStatisticsEnable); (env)->SetIntField(objectNewEng, nStatisticsLevel, stEngInfoStat.nStatisticsLevel); (env)->SetIntField(objectNewEng, nRptIntervalInMs, stEngInfoStat.nRptIntervalInMs); (env)->SetIntField(objectNewEng, nSpeakMode, stEngInfoStat.nSpeakMode); (env)->SetIntField(objectNewEng, nJavaAPILevel, stEngInfoStat.nJavaAPILevel); <span style="white-space:pre"> </span>}
jclass obVoiceAPI = (env)->FindClass("com/qq/qtx/EngRunInfoStat$VoiceAPIStat"); jmethodID objectVoiceInitID = (env)->GetMethodID(obRecordMode, "<init>", "(Lcom/qq/qtx/EngRunInfoStat;)V"); jobject objectVoice = (env)->NewObject(obVoiceAPI, objectVoiceInitID, objectNewEng);
创建实例后,就可以给内嵌类的成员变量赋值操作了,示例代码如下:jfieldID nSLESRecOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESRecOpenSucceedCnt", "I"); jfieldID nSLESRecOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESRecOpenFailedCnt", "I"); jfieldID nSLESPlayOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESPlayOpenSucceedCnt", "I"); jfieldID nSLESPlayOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESPlayOpenFailedCnt", "I"); jfieldID nJAVARecOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVARecOpenSucceedCnt", "I"); jfieldID nJAVARecOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVARecOpenFailedCnt", "I"); jfieldID nJAVAPlayOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVAPlayOpenSucceedCnt", "I"); jfieldID nJAVAPlayOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVAPlayOpenFailedCnt", "I");
赋值完成后,我们还需要将创建内嵌类ObjectVoice用SetObjectField()函数设置到创建到外部类中,这样外部类中内嵌类才起作用是有效的,示例代码如下:<span style="white-space:pre"> </span>(env)->SetIntField(objectVoice, nSLESRecOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nSLESRecOpenSucceedCnt); (env)->SetIntField(objectVoice, nSLESRecOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nSLESRecOpenFailedCnt); (env)->SetIntField(objectVoice, nSLESPlayOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nSLESPlayOpenSucceedCnt); (env)->SetIntField(objectVoice, nSLESPlayOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nSLESPlayOpenFailedCnt); (env)->SetIntField(objectVoice, nJAVARecOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nJAVARecOpenSucceedCnt); (env)->SetIntField(objectVoice, nJAVARecOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nJAVARecOpenFailedCnt); (env)->SetIntField(objectVoice, nJAVAPlayOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nJAVAPlayOpenSucceedCnt); (env)->SetIntField(objectVoice, nJAVAPlayOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nJAVAPlayOpenFailedCnt);
至此JNI层函数里面对于Java结构体类就创建赋值完成了,最后通过函数返回给Java层就可以了,JNI函数完成代码示例如下:(env)->SetObjectField(objectNewEng, stVoiceApiStat, objectVoice);
/* * Class: com_test_eng_jni_NativeMethodJNI * Method: GetRunInfoStat * Signature: ()Lcom/test/eng/EngRunInfoStat; */ JNIEXPORT jobject JNICALL Java_com_test_eng_jni_NativeMethodJNI_GetRunInfoStat(JNIEnv *env, jclass obj) { EngRunInfoStat stEngInfoStat; memset(&stEngInfoStat, 0, sizeof(EngRunInfoStat)); //获取Java实例 jclass objectClass = (env)->FindClass("com/tess/eng/EngRunInfoStat"); //获取成员变量 jmethodID objectClassInitID = (env)->GetMethodID(objectClass, "<init>", "()V"); jobject objectNewEng = (env)->NewObject(objectClass, objectClassInitID); jfieldID bStatisticsEnable = (env)->GetFieldID(objectClass, "bStatisticsEnable", "Z"); jfieldID nStatisticsLevel = (env)->GetFieldID(objectClass, "nStatisticsLevel", "I"); jfieldID nRptIntervalInMs = (env)->GetFieldID(objectClass, "nRptIntervalInMs", "I"); jfieldID nSpeakMode = (env)->GetFieldID(objectClass, "nSpeakMode", "I"); jfieldID nJavaAPILevel = (env)->GetFieldID(objectClass, "nJavaAPILevel", "I"); jfieldID stVoiceApiStat = (env)->GetFieldID(objectClass, "stVoiceApiStat", "Lcom/test/eng/EngRunInfoStat$VoiceAPIStat;"); jclass obVoiceAPI = (env)->FindClass("com/qq/qtx/EngRunInfoStat$VoiceAPIStat"); jmethodID objectVoiceInitID = (env)->GetMethodID(obRecordMode, "<init>", "(Lcom/test/eng/EngRunInfoStat;)V"); jobject objectVoice = (env)->NewObject(obVoiceAPI, objectVoiceInitID, objectNewEng); jfieldID nSLESRecOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESRecOpenSucceedCnt", "I"); jfieldID nSLESRecOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESRecOpenFailedCnt", "I"); jfieldID nSLESPlayOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESPlayOpenSucceedCnt", "I"); jfieldID nSLESPlayOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nSLESPlayOpenFailedCnt", "I"); jfieldID nJAVARecOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVARecOpenSucceedCnt", "I"); jfieldID nJAVARecOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVARecOpenFailedCnt", "I"); jfieldID nJAVAPlayOpenSucceedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVAPlayOpenSucceedCnt", "I"); jfieldID nJAVAPlayOpenFailedCnt = (env)->GetFieldID(obVoiceAPI, "nJAVAPlayOpenFailedCnt", "I"); if(0 == GetRunInfoStat(&stEngInfoStat)) { (env)->SetBooleanField(objectNewEng, bStatisticsEnable, (jboolean)stEngInfoStat.bStatisticsEnable); (env)->SetIntField(objectNewEng, nStatisticsLevel, stEngInfoStat.nStatisticsLevel); (env)->SetIntField(objectNewEng, nRptIntervalInMs, stEngInfoStat.nRptIntervalInMs); (env)->SetIntField(objectNewEng, nSpeakMode, stEngInfoStat.nSpeakMode); (env)->SetIntField(objectNewEng, nJavaAPILevel, stEngInfoStat.nJavaAPILevel); (env)->SetIntField(objectVoice, nSLESRecOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nSLESRecOpenSucceedCnt); (env)->SetIntField(objectVoice, nSLESRecOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nSLESRecOpenFailedCnt); (env)->SetIntField(objectVoice, nSLESPlayOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nSLESPlayOpenSucceedCnt); (env)->SetIntField(objectVoice, nSLESPlayOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nSLESPlayOpenFailedCnt); (env)->SetIntField(objectVoice, nJAVARecOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nJAVARecOpenSucceedCnt); (env)->SetIntField(objectVoice, nJAVARecOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nJAVARecOpenFailedCnt); (env)->SetIntField(objectVoice, nJAVAPlayOpenSucceedCnt, stEngInfoStat.stVoiceApiStat.nJAVAPlayOpenSucceedCnt); (env)->SetIntField(objectVoice, nJAVAPlayOpenFailedCnt, stEngInfoStat.stVoiceApiStat.nJAVAPlayOpenFailedCnt); } (env)->SetObjectField(objectNewEng, stVoiceApiStat, objectVoice); return objectNewEng; }
- Java函数传递结构体参数
参数为结构体传递和上面返回值类似,本次先介绍到这里,后面需要的时候在续写参数为结构体情况。