JNI的数据类型和类型签名
数据类型
JNI的数据类型包含两种:基本类型和引用类型。
基本类型主要有jboolean
、jchar
、jint
等,它们和Java中的数据类型的对应关系如下表所示。
JNI中的引用类型主要有类、对象和数组,它们和Java中的引用类型的对应关系如下表所示。
当然,JNI 中还有个 Java 中没有的 jsize
,定义如下:
typedef jint jsize;
其实jsize
整型是用来描述基本指标和大小,没有什么神秘的。
类型签名
JNI的类型签名标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。
类的签名比较简单,它采用 “L+包名+类名+;
” 的形式,只需要将其中的.
替换为/
即可。比如java.lang.String
,它的签名为Ljava/lang/String;
,注意末尾的;
也是签名的一部分。
基本数据类型的签名采用一系列大写字母来表示,如下表所示。
从上表可以看出,基本数据类型的签名是有规律的,一般为首字母的大写,但是boolean
除外,因为B
已经被byte
占用了,而long
的签名之所以不是L
,那是因为L
表示的是类的签名。
对象和数组的签名稍微复杂一些。对于对象来说,它的签名就是对象所属的类的签名,比如String
对象,它的签名为Ljava/lang/String;
。对于数组来说,它的签名为[+类型签名
,比如int
数组,其类型为int
,而int
的签名为I
,所以int
数组的签名就是[I
,同理就可以得出如下的签名对应关系:
char[] [C
float[] [F
double[] [D
long[] [J
String[] [Ljava/lang/String;
Object[] [Ljava/lang/Object;
对于多维数组来说,它的签名为n个[+类型签名
,其中n表示数组的维度,比如,int[][]
的签名为[[I
,其他情况可以依此类推。
方法的签名为(参数类型签名)+返回值类型签名
,这有点不好理解。举个例子,如下方法:boolean fun1(int a, double b, int[] c)
,根据签名的规则可以知道,它的参数类型的签名连在一起是ID[I
,返回值类型的签名为Z
,所以整个方法的签名就是(ID[I)Z
。再举个例子,下面的方法:boolean fun1(int a, String b, int[] c)
,它的签名是(ILjava/lang/String; [I)Z
。为了能够更好地理解方法的签名格式,下面再给出两个示例:
int fun1() 签名为 ()I
void fun1(int i) 签名为 (I)V
一个Java类的方法的Signature可以通过javap
命令获取:javap -s -p Java类名
本地方法中访问java程序中的内容
1. 访问 String 对象
从java程序中传过去的String对象在本地方法中对应的是jstring
类型,jstring
类型和c中的char*
不同,所以如果你直接当做 char*
使用的话,就会出错。因此在使用之前需要将jstring
转换成为c/c++中的char*
,这里使用JNIEnv的方法转换。下面是一个例子:
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
char buf[128];
const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
}
这里使用GetStringUTFChars
方法将传进来的prompt
(jstring
类型)转换成为UTF-8
的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars
方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是访问String的一些方法:
GetStringUTFChars
将jstring
转换成为UTF-8
格式的char*
GetStringChars
将jstring
转换成为Unicode
格式的char*
ReleaseStringUTFChars
释放指向UTF-8
格式的char*
的指针ReleaseStringChars
释放指向Unicode
格式的char*
的指针NewStringUTF
创建一个UTF-8
格式的String
对象NewString
创建一个Unicode
格式的String
对象GetStringUTFLength
获取UTF-8
格式的char*
的长度GetStringLength
获取Unicode
格式的char*
的长度
2. 访问 Array 对象
和String对象一样,在本地方法中不能直接访问jarray
对象,而是使用JNIEnv
指针指向的一些方法来使用。
访问Java原始类型数组:
- 1)获取数组的长度:
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
int i, sum = 0;
jsize len = (*env)->GetArrayLength(env, arr);
}
这里获取数组的长度和普通的c语言中的获取数组长度不一样,这里使用JNIEnv
的一个函数GetArrayLength
。
- 2)获取一个指向数组元素的指针:
jint *body = (*env)->GetIntArrayElements(env, arr, 0);
使用GetIntArrayElements
方法获取指向arr
数组元素的指针,注意该函数的参数,第一个是JNIEnv
,第二个是数组,第三个是数组里面开始的元素。
- 3)使用指针取出 Array 中的元素
for (i=0; i<len; i++) {
sum += body[i];
}
这里使用就和普通的c中的数组使用没有什么不同了
- 4)释放数组元素的引用
(*env)->ReleaseIntArrayElements(env, arr, body, 0);
和操作String中的释放String的引用是一样的,提醒JVM回收arr数组元素的引用。
这里举的例子是使用int数组的,同样还有boolean、float等对应的数组。
获取数组和释放数组元素指针的对应关系:
数组类型 | 获取函数 | 释放函数 |
---|---|---|
boolean | GetBooleanArrayElements | ReleaseBooleanArrayElements |
byte | GetByteArrayElements | ReleaseByteArrayElements |
char | GetCharArrayElements | ReleaseCharArrayElements |
short | GetShortArrayElements | ReleaseShortArrayElements |
int | GetIntArrayElements | ReleaseIntArrayElements |
long | GetLongArrayElements | ReleaseLongArrayElements |
float | GetFloatArrayElements | ReleaseFloatArrayElements |
double | GetDoubleArrayElements | ReleaseDoubleArrayElements |
GetObjectArrayElement
returns the object element at a given index.SetObjectArrayElement
updates the object element at a given index.
3. 访问Java对象的方法
JNI调用Java方法的流程是先通过类名找到类,然后再根据方法名找到方法的id
,最后就可以调用这个方法了。如果是调用Java中的非静态方法,那么需要构造出类的对象后才能调用它。
在本地方法中调用Java对象的方法的步骤:
① 获取你需要访问的Java对象的Class
类:
jclass cls = (*env)->GetObjectClass(env, obj);
使用GetObjectClass
方法获取obj
对应的jclass
。
② 获取MethodID
:
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
使用GetMethdoID
方法获取你要使用的方法的MethdoID
。其参数的意义:
env
:JNIEnv
cls
:第一步获取的jclass
"callback"
:要调用的方法名"(I)V"
:方法的Signature
③ 调用方法:
(*env)->CallVoidMethod(env, obj, mid, depth);
使用CallVoidMethod
方法调用方法。参数的意义:
env
:JNIEnv
指针obj
:调用该native方法的jobject
对象mid
:方法的methodID
(即第二步获得的MethodID
)depth
:方法需要的参数(对应方法的需求,添加相应的参数),可以是可变参数
注:这里使用的是CallVoidMethod
方法调用,因为没有返回值,如果有返回值的话要使用对应的方法。
除了CallVoidMethod
外,针对每种基本类型的方法都有不同的重载,如下:
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, .