Android NDK开发之旅(3):详解JNI数据类型与C/C++、Java之间的互调

(码字不易,转载请声明出处:http://blog.csdn.net/andrexpert/article/details/72851294)

1. JNI数据类型

      JNI,Java NativeInterface,是一种为Java编写本地方法和JVM嵌入本地应用程序标准的应用程序接口,它允许运行在JVM上的Java代码能够与C/C++实现的本地库进行交互。

(1) JNI数据类型

     Java中有两种类型:基本数据类型(int、float、char等)和引用类型(类、对象、数组等)。JNI定义了一个C/C++类型的集合,集合中每一个类型对应于Java中的每一个类型,其中,对于基本类型而言,JNI与Java之间的映射是一对一的,比如Java中的int类型直接对应于C/C++中的jint;而对引用类型的处理却是不同的,JNI把Java中的对象当作一个C指针传递到本地函数中,这个指针指向JVM中的内部数据结构,而内部数据结构在内存中的存储方式是不可见的,本地代码必须通过在JNIEnv中选择适当的JNI函数来操作JVM中的对象。比如,对于java.lang.String对应的JNI类型是jstring,但本地代码只能通过GetStringUTFChars这样的JNI函数来访问字符串的内容。以下是JNI数据类型映射关系表,通过这种映射JNI就可以正确识别并转换Java数据类型:

  映射

 类型

Java类型

JNI本地类型

C类型

type/bits

type/bits/signatures

type/bits

 

 

 

基本

类型

boolean

8

jboolean

u8

Z

-

 

byte

8

jbyte

8

B

-

 

char

16

jchar

u16

C

char

8

short

16

jshort

16

S

short

16

int

32

jint

32

I

int

32

long

64

jlong

64

J

long

32

float

32

jfloat

32

F

float

32

double

64

jdouble

64

D

double

64

 

void

-

Void

N/A

V

void

-

 

 

 

 

 

 

引用

类型

Object

-

jobject

-

-

-

-

Class

-

jclass

-

L fully-qualified-class;

-

-

String

-

jstring

-

Ljava/lang/String;

-

-

arrays

-

jarray

-

-

-

-

object arrays

-

jobjectArray

-

[L fully-qualified-class;

-

-

boolean arrays

-

jbooleanArray

-

[Z

-

-

byte arrays

-

jbyteArray

-

[B

-

-

char arrays

-

jcharArray

-

[C

-

-

short arrays

-

jshortArray

-

[S

-

-

int arrays

-

jintArray

-

[I

-

-

long arrays

-

jlongArray

-

[J

-

-

float arrays

-

jfloatArray

-

[F

-

-

double arrays

-

jdoubleArray

-

[D

-

-

Throwable

-

jthrowable

-

Ljava/lang/Throwable;

-

-

 

      注:u8表示unsigned 8 bits;u16表示unsigned 16 bits;由于Java支持方法重载,在JNI访问Java层方法时仅靠函数名是无法唯一确定一个方法,因此JNI提供了一套签名规则(如:Z、B、[Z等),用一个字符串来唯一确定一个方法,其规则:(参数1类型签名参数2类型签名…)返回值类型签名,比如Java方法long getDeviceId(int n, String s, int[] arr)、long getDeviceId(int n)的类型签名分别为(ILjava/lang/String;[I)J、(I)J。

(2) 函数原型解析

JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_ getDeviceId            (JNIEnv*env, jobjectjobj, jstring j_str)或JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_ getDeviceId             (JNIEnv*env, jclass jcls,jstring j_str)

     以上两个函数原型分别为两个Java本地方法到JNI层的映射,它映射的规则是:JNIEXPORT返回值类型JNICALL Java_包名_类名_方法名(JNIEnv*,jobject,参数1类型,…),其中JNIEXPORT和JNICALL为JNI的关键字,表示此函数是要被JNI调用的;JNIEnv接口指针指向一个函数表,函数表中的每一个入口指向一个JNI函数,因此通过该指针调用相关函数实现对Java引用的访问;jobject和jclass类型参数分别表明该Java本地方法是静态方法还是非静态方法,且jobject表示该非静态方法所属的对象,jclass表示该静态方法所属的类。

2. Java调用C/C++本地函数

(1) C or C++

/** *  CPP源文件:Java访问本地函数,返回一个字符串 * */JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_getStringFromC  (JNIEnv *env, jclass jobj){       return env->NewStringUTF("My Nameis jiangdongguo--2017");} /** *  C源文件:Java访问本地函数,返回一个字符串 * */JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_getStringFromC  (JNIEnv *env, jclass jobj){       return (*env)->NewStringUTF(env,"MyName is jiangdongguo--2017");}

       上面两个函数原型,逻辑代码结构相似,其作用均为当Java层调用本地方法时向Java层返回一个UTF-8格式的字符串。然后,实际上它们的实现是截然不同的,比如一个需要传递env指针变量,另一个不需要,此外它们调用NewStringUTF的方式也不一样,究其原因,主要是因为这两个函数是在不同的源文件中实现的。通过查看jni.h源码可知,当源文件为.cpp时,JNIEnv实际为结构体JNIEnv_,然后我们再查看JNIEnv_结构体的内容,找到NewStringUTF(constchar *utf)函数,它实际执行了functions->NewStringUTF(this,utf)函数,而这个函数默认传递了一个this参数,该this参数则指的是getStringFromC函数原型中JNIEnv指针变量参数。因此,使用C++开发JNI时就无需再传递JNIEnv指针变量且在使用JNIEnv_结构体的成员时,直接使用结构体变量指向成员即可。

#ifdef__cplusplus// 如果为C++,JNIEnv表示JNIEnv_typedef JNIEnv_  JNIEnv;#else// 如果不为C++,JNIEnv表示JNINativeInterface_*typedef const struct JNINativeInterface_ * JNIEnv;#endif structJNIEnv_ {    const struct JNINativeInterface_*functions;#ifdef__cplusplus……jstringNewStringUTF(const char *utf) {        returnfunctions->NewStringUTF(this,utf);}……#endif

       当源文件为.c时,JNIEnv实际表示的JNINativeInterface_*,JNIEnv*env即JNINativeInterface_**env,因此,我们在调用JNINativeInterface_结构体中的成员时需要使用一级指针来实现,即(*env)->成员。然后,再继续查看JNINativeInterface_源码,NewStringUTF函数需要传入一个JNIEnv结构体类型指针变量,该指针变量指向JNINativeInterface_结构体存储的地址,因此,还需要将变量env赋值给NewStringUTF即可。

structJNINativeInterface_ {       ……       jstring (JNICALL *NewStringUTF) (JNIEnv*env, const char *utf);       ….}

(2)  JNI中字符串处理

     在JNI开发中,jstring类型指向JVM内部的一个字符串和C字符串类型char*是不同的,前者编码格式为Unicodec(UTF-16),后者为UTF-8。因此,我们不能简单的将jstring当作一个普通的C字符串来使用,必须要使用合适的JNI函数把jstring转化为C/C++字符串,即GetStringUTFChars。该函数可以把一个jstring指针(指向JVM内部的Unicode字符序列),转化为一个UTF-8格式的C字符串。需要注意的是,如果jstring字符串中包含中文,还需要将UTF-8转化为GB2312,否则会出现中文乱码。

JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_getString2FromC  (JNIEnv *env, jobject jobj, jstring j_str){       jsize len =env->GetStringLength(j_str);       const char* c_str = (*env)->GetStringUTFChars(env,j_str,JNI_FALSE);       // 内存不足,抛出OOM异常if(c_str == NULL){       return NULL;}// const char*转char*,拼接字符串char *c_tmp = (char *)malloc(len);stpcpy(c_tmp,c_str);strcat(c_tmp," 广州");// char * 转stringjstring j_temp = (*env)->NewStringUTF(env,(constchar*)c_tmp);// 释放本地字符串资源(*env)->ReleaseStringUTFChars(env,j_str,c_str);// 释放指针指向的内存资源free(c_tmp);return j_temp;}

      注意:从GetStringUTFChars中获取的UTF-8字符串在本地代码中使用完毕后,要使用ReleaseStringUTFChars告诉JVM这个UTF-8字符串不会被使用了,因为这个UTF-8字符串占用的内存会被回收。

3.  C/C++访问Java层方法、属性

    JNI中提供了一系列函数可以用于访问对象的属性或者类的属性(静态属性),不仅可以获得该属性的值,还可以在本地代码中修改属性的值。总的来说,为了访问Java层的属性,本地方法需要做以下两步:

    首先,如果是访问对象的属性,需要利用Java层传入本地的obj调用GetObjectClass获取该属性的类引用;

    jclasscls = (*env)->GetObjectClass(env,jobj);

    其次,通过在类引用上调用GetFieldID获取属性ID、属性名字和属性描述符,如果是静态字段,则是调用GetStaticFieldId方法。其中,key、count是属性的名称,"Ljava/lang/String;"或"I"是属性的类型;

     jfieldID key_fid = (*env)->GetFieldID(env,cls,"key","Ljava/lang/String;");

     jfieldID count_fid = (*env)->GetStaticFieldID(env, cls,"count","I");

     第三,将对象obj和字段ID作为参数传入来访问属性,得到属性的值。其中,XXX为基本数据类型;

     jstringkey = (jstring)(*env)->GetObjectField(env,jobj,对象属性ID);

     jXXXarray arr = (jXXXarray)(*env)->GetXXXField(env,jobj,属性ID);;

     jXXX count= (*env)->GetStaticXXXField(env,ju_cls,静态属性ID);

最后,如果我们需要修改属性的值,可以通过setXXXField或setStaticXXXField来修改;

(1)  C/C++层访问对象的属性

/** *  C/C++层访问Java对象的属性 * */JNIEXPORTjstring JNICALL Java_com_jiangdg_jnilearning_JNIUitls_accessJavaField        (JNIEnv *env, jobject jobj){    // 得到Java类JNIUtils.class    jclass jniutil_cls = (*env)->GetObjectClass(env,jobj);    // 得到java对象的key属性ID    jfieldID key_fid =(*env)->GetFieldID(env,jniutil_cls,"key","Ljava/lang/String;");    // 得到Java对象Key属性的值    jstring key =(jstring)(*env)->GetObjectField(env,jobj,key_fid);    // 拼接一个新的c字符串    const char * c_key =(*env)->GetStringUTFChars(env,key,JNI_FALSE);    char c_temp[100] = "Hello,";    strcat(c_temp,c_key);    // 修改key属性的值    jstring j_temp =(*env)->NewStringUTF(env,c_temp);   (*env)->SetObjectField(env,jobj,key_fid,j_temp);    return j_temp;}

(2)  C/C++层访问Java类的静态属性

/** *  C/C++层访问Java类的静态属性 * */JNIEXPORT voidJNICALL Java_com_jiangdg_jnilearning_JNIUitls_accessJavaStaticField        (JNIEnv *env, jobject jobj){    jclass ju_cls =(*env)->GetObjectClass(env,jobj);    // 得到JNIUtils类静态属性count的ID    jfieldID count_fid =(*env)->GetStaticFieldID(env,ju_cls,"count","I");    // 得到count属性的值    jint count =(*env)->GetStaticIntField(env,ju_cls,count_fid);    // 修改count属性的值    jint new_count = count+1;   (*env)->SetStaticIntField(env,ju_cls,count_fid,new_count);}

(3)  C/C++层访问Java对象的方法

/** *  C/C++层访问Java对象的方法 * */JNIEXPORT voidJNICALL Java_com_jiangdg_jnilearning_JNIUitls_accessJavaMethod        (JNIEnv *env, jobject jobj){    jclass cls =(*env)->GetObjectClass(env,jobj);    // 得到JNIUtils类对象genRandomInt方法的ID    jmethodID mid =(*env)->GetMethodID(env,cls,"genRandomInt","(I)I");    // 调用genRandomInt方法    jint random =(*env)->CallIntMethod(env,jobj,mid,200);    LOG_I("genRandomInt() =%d",random);}

(4)  C/C++层访问Java类的静态方法

/** * C/C++层访问Java类的静态方法 * */JNIEXPORT voidJNICALL Java_com_jiangdg_jnilearning_JNIUitls_accessJavaStaticMethod        (JNIEnv *env, jobject jobj){    jclass ju_cls =(*env)->GetObjectClass(env,jobj);    // 得到JNIUtils类getUUID静态方法ID    jmethodID mid =(*env)->GetStaticMethodID(env,ju_cls,"getUUID","()Ljava/lang/String;");    // 调用getUUID方法    jstring UUID =(jstring)(*env)->CallStaticObjectMethod(env,ju_cls,mid);    LOG_I("getUUID() =%s",(*env)->GetStringUTFChars(env,UUID,JNI_FALSE));}

(5)  C/C++层实现指向子类对象访问父类的方法

/** * C/C++访问Java的父类方法 * */JNIEXPORT voidJNICALL Java_com_jiangdg_jnilearning_JNIUitls_accessJavaFatherMethod        (JNIEnv *env, jobject jobj){    jclass cls =(*env)->GetObjectClass(env,jobj);    // 得到JNIUtils对象fruitInfo属性对象的ID    jfieldID fid = (*env)->GetFieldID(env,cls,"fruitInfo","Lcom/jiangdg/jnilearning/Fruit;");    // 得到fruitInfo属性对象    jobject fruit_jobj =(*env)->GetObjectField(env,jobj,fid);    // 得到父类Fruit    jclass fruit_cls =(*env)->FindClass(env,"com/jiangdg/jnilearning/Fruit");    // 得到父类的printEatInfo方法,调用该方法    jmethodID fruit_mid =(*env)->GetMethodID(env,fruit_cls,"printEatInfo","()V");    (*env)->CallNonvirtualVoidMethod(env,fruit_jobj,fruit_cls,fruit_mid);}

4. 效果演示



GitHub项目地址:https://github.com/jiangdongguo/JniLearning


更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. 箭头函数的基础使用
  3. Python技巧匿名函数、回调函数和高阶函数
  4. Python list sort方法的具体使用
  5. python list.sort()根据多个关键字排序的方法实现
  6. android上一些方法的区别和用法的注意事项
  7. 浅析android通过jni控制service服务程序的简易流程
  8. android实现字体闪烁动画的方法
  9. Android中dispatchDraw分析

随机推荐

  1. 使用rpm管理软件包
  2. Linux企业生产常见问题集合(一)答案
  3. 软链接于硬连接
  4. Linux扩容分区操作过程
  5. 友侃有笑公众号Linux系统学习路线图(全)
  6. 报表分析软件有哪些呢?不急不急,给你推荐几
  7. 练习2-10 计算分段函数[1] (10分)
  8. Redis 高可用篇:你管这叫主从架构数据一致
  9. 数据中心
  10. 选择器优先级/前端组件样式模块化原理与