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. 效果演示

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


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


更多相关文章

  1. Android NDK环境创建方法简介
  2. Unity3D和Android之间的方法交互(jar模式)
  3. Android之LocationManager类的方法详解
  4. android init.rc 修改方法---adb shell 不能修改的原因
  5. android:configChanges属性在不同版本SDK下需要注意的事项

随机推荐

  1. 急死我了!我以前用DREAMWEAVER编写的HTML
  2. HTML,CSS - 长表的行中断需要修复
  3. 样式通常以相同的组合出现:创建单个类还是
  4. 如何在Java Swing中的JPanel中呈现基本的
  5. 单个元素html上的多个类
  6. 如何使用jQuery在我的网站演示中自动填写
  7. JQuery基于元素的高度添加类
  8. HTML之表格篇——表格的嵌套
  9. 怎么动态设置html的canvas元素的尺寸?
  10. 如何在bootstrap中添加汉堡包菜单