JNI/NDK入门指南之JNI数据类型,描述符详解
Android JNI/NDK入门指南目录
JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用
JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二
引言
通过前面的章节正确姿势了解Android中JNI/NDK,我想读者朋友们一定对JNI/NDK的基本概念印象深刻了吗,那么接下来就要开始我们的JNI编程学习了。学编程的东西,一般法则先学习它的最基础数据类型,同理JNI也是如此。既然JNI是用来C/C++和Java通信的,那么JNI为之定义了一系列基本数据类型和引用数据类型用来与Java交织呼应。那么在接下来的篇章里面我们会围绕JNI的数据类型和描述符为中心而开展。
注意:这里都是以32位CPU架构来说明!
基本数据类型
先从最简单的基本数据类型开始,看看JNI语法为其定义的相关法则。
Java Launage Type | JNI Native Type | C/C++ Launage Type | Type Description |
---|---|---|---|
boolean | jboolean | unsigned char | unsigned 8 bits |
byte | jbyte | signed char | signed 8 bits |
char | jchar | unsigned short | unsigned 16 bits |
short | jshort | signed short | signed 16 bits |
int | jint | signed int | signed 32 bits |
long | jlong | signed long | signed 64 bits |
float | jfloat | float | 32 bits |
double | jdouble | double | 64 bits |
注意:这些数据类型是可以直接在JNI中直接使用的,不需要进行转换。
jbyte result=0xff;
jint size;
jbyte* timeBytes;
引用数据类型
Java Launage Type | JNI Native Type | Type Description |
---|---|---|
java.lang.Object | jobject | 可以表示任何Java的对象,或者没有 JNI对应类型的Java对象(实例方法的强制参数) |
java.lang.String | jstring | Java的String字符串类型的对象 |
java.lang.Class | jclass | Java的Class类型对象(静态方法的强制参数) |
Object[] | jobjectArray | Java任何对象的数组 |
boolean[] | jbooleanArray | Java boolean型数组 |
byte[] | jbyteArray | Java byte型数组 |
char[] | jcharArray | Java char型数组 |
short[] | jshortArray | Java short型数组 |
int[] | jintArray | Java int型数组 |
long[] | jlongArray | Java long型数组 |
float[] | jfloatArray | Java float型数组 |
double[] | jdoubleArray | Java double型数组 |
java.lang.Throwable | jthrowable | Java的Throwable类型,表示异常的所有类型和子类 |
void | void | N/A |
注意:
(1) JNI中与Java字符串String相对应的jstring也是引用类型,这个切记。
(2) 数组也是引用数据类型,引用类型不能直接使用,要经过JNI函数转换才能使用。参见如下代码:
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass("[I");
int len = 10;
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为len
jobjectArray obejctIntArray = env->NewObjectArray(len,intArrayClass , NULL);
和Java中的继承关系类似,JNI引用类型也存在一个继承关系,当我们在进行JNI实际开发的时候,可以参照进行相对应的转换:
类描述符
假设这么一个场景,在JNI的Native方法中,我们要使用Java中的对象怎么办(包括你自定义的或者Android源码中已有的)?即在C/C++中怎么找到Java中的类,这就要使用到JNI开发中的类描述符了。JNI提供的函数中有个FindClass()就是用来查找Java类的,其参数必须放入一个类描述符字符串,类描述符一般是类的完整名称(包名+类名)。这么说是不是很抽象,下面我举几个栗子说明一下:
Java中String类
完整类名: java.lang.String
对应类描述符: java/lang/String
即一个 Java 类对应的描述符,就是类的全名,其中 . 要换成 / ,最后 不要忘掉末尾的分号。
其实,在实践中,我发现可以直接用该类型的域描述符取代,也是可以成功的。
例如: jclass intArrCls = env->FindClass(“java/lang/String”)
等同于: jclass intArrCls = env->FindClass(“Ljava/lang/String;”)
Android中Surface类
完整类名: android.view.Surface
对应类描述符: android/view/Surface
即一个 Android 类对应的描述符,就是类的全名,其中 . 要换成 /
int err = RegisterMethodsOrDie(env, "android/view/Surface",
gSurfaceMethods, NELEM(gSurfaceMethods));
jclass clazz = FindClassOrDie(env, "android/view/Surface");
域描述符
看到这个名词是不是感觉有点懵逼,啥是域描述符。让我为你一一道来,域描述符是JNI中对Java数据类型的一种表示方法(就是对Java类中的变量,在JNI世界的定义),即在JVM虚拟机中,存储数据类型的名称时,是使用指定的描述符来存储,而不是我们习惯的 int,float 等。虽然有类描述符,但是类描述符里并没有说明基本类型和引用数据类型如何表示,所以在JNI中就引入了域描述符的概念。
1 基本类型域描述符
JNI在域描述符中已经定义好了基本类型的表示,其对照表格如下:
Java Launage Type | Field Description |
---|---|
int | I |
long | J |
byte | B |
short | S |
char | C |
double | D |
boolean | Z |
void | V |
其它引用类型 | L+类全名+; |
数组 | [ |
方法 | (参数)返回值 |
2 引用类型域描述符
一般引用类型则为 L + 该类型类描述符 + ; (注意,这儿的分号“;”只得是JNI的一部分,而不是我们汉语中的分段,下同)。这么说自我感觉也有那么点抽象,让我们举几个栗子说明一下:
2.1 String引用类型域描述符
Java类型: java.lang.String
JNI 域描述符:Ljava/lang/String;
扩展开来就是 即一个 Java 类对应的描述符,就是 L 加上类的全名,其中 . 要换成 / ,最后 不要忘掉末尾的分号。是不是有点按图索骥的感觉,那么让我么下面接着照葫芦画瓢再接再厉。
2.2 数组引用类型域描述符
对于数组,其通用规则为为 : [ + 其元素类型的域描述符,二维数组就是两个 [ ,以此类推,n维数组就有几个[ + 其元素类型的域描述符。
Java类型: int[]
JNI域描述符: [I
Java类型: float[]
JNI域描述符: [F
Java类型: String[]
JNI域描述符: [Ljava/lang/String;
Java类型: Object[ ]
JNI域描述符: [[Ljava/lang/Object;
多维数组则是 n个[ +该元素域描述符 , N代表的是几维数组。例如:
Java类型: int[][]
JNI域描述符: [[I
Java类型: float[][]
JNI域描述符: [[F
get_field(env, javaBean_clazz, "boolValue", "Z", &javaBean_t.boolValue);
get_field(env, javaBean_clazz, "charValue", "C", &javaBean_t.charValue);
get_field(env, javaBean_clazz, "doubleValue", "D", &javaBean_t.doubleValue);
get_field(env, javaBean_clazz, "intValue", "I", &javaBean_t.intVaule);
get_field(env, javaBean_clazz, "array", "[B", &javaBean_t.byteArray);
get_field(env, javaBean_clazz, "mDoubleDimenArray", "[[I", &javaBean_t.double_dimen_array);
get_field(env, javaBean_clazz, "stringValue", "Ljava/lang/String;", &javaBean_t.stringValue);
get_field(env, javaBean_clazz, "mInnerClass", "Lcom/xxx/object2struct/JavaBean$InnerClass;", &javaBean_t.inner_message);
方法描述符
Java世界的类,变量都在JNI世界找到了对应的规则了,各位读者亲朋友们,有没有发现Java类中的方法是不是还没有一个对应的法则呢,本章节就要来说JNI中对应Java方法的方法描述符了。方法描述符定义了方法的返回值和参数的表示形式,将参数类型的域描述符按声明顺序放入一对括号中(如果没有参数则不需要括号),括号后跟返回值类型的域描述符即形成方法描述符。我想各位对方法描述符的定义有了一定理解了,那么让我们来举几个栗子说明一下:
Java Method | Method Description |
---|---|
String fun() | ()Ljava/lang/String; |
int fun(int i, Object object) | (ILjava/lang/Object;)I |
void fun(byte[ ] bytes) | ([B)V |
int fun(byte data1, byte data2) | (BB)I |
void fun() | ()V |
注意,void的处理比较特殊,如果返回值为void,那么方法描述符中必须使用V表示,当void作为参数的时候,忽略。
static JNINativeMethod gMethods[] = {
{"getJavaBeanFromNative", "()Lcom/xxx/object2struct/JavaBean;",(void*)Java_com_xxx_object2struct_JniTransfer_getJavaBeanFromNative },
{"transferJavaBeanToNative", "(Lcom/xxx/object2struct/JavaBean;)V",(void*)Java_com_xxx_object2struct_JniTransfer_transferJavaBeanToNative },
};
补充点:当我们使用JDK内置工具javah生成JNI的头文件的时候(当然这个工具得配置一下才能使用),我们会发现javah头文件注释中已经生成了本地方法的方法描述符,但是它偏不叫方法描述符而是叫做Signature,所以我们也可以把方法描述符直译为签名,形如:
#include <jni.h>
/* Header for class com_xxx_xxxndk_XxxNative */
#ifndef _Included_com_xxx_xxxndk_XxxNative
#define _Included_com_xxx_xxxndk_XxxNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xxx_xxxndk_XxxNative
* Method: getCurrentTime
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xxx_xxxndk_XxxNative_getCurrentTime
(JNIEnv *, jobject);
/*
* Class: com_xxx_apifunctest_test_XxxNative
* Method: Prn_Vline
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_xxx_apifunctest_test_XxxNative_Prn_1Vline
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
写在最后
通过本章的细述,我想各位读者朋友们一定对JNI的数据类型,以及描述符有了比较详细的了解了。在本篇章的最后,我们有说到了javah工具,这个我们会在后面的章节里面详细介绍的。不要着急。青山不改绿水长流,各位江湖见!