JNI 的数据类型以及和Java层之间的数据转换

JNI的数据类型和类型签名

数据类型

JNI的数据类型包含两种:基本类型引用类型

基本类型主要有jbooleanjcharjint等,它们和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方法将传进来的promptjstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。

注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。

下面是访问String的一些方法:

  • GetStringUTFCharsjstring转换成为UTF-8格式的char*
  • GetStringCharsjstring转换成为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。其参数的意义:

  • envJNIEnv
  • cls:第一步获取的jclass
  • "callback":要调用的方法名
  • "(I)V":方法的Signature

③ 调用方法:

(*env)->CallVoidMethod(env, obj, mid, depth);

使用CallVoidMethod方法调用方法。参数的意义:

  • envJNIEnv指针
  • 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, .
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值