JNI基础知识

JNI(Java Native Interface),它是java中的一套接口,用来跟c和c++通信。

JNI中的数据类型

java中的数据类型和c的数据类型之间的映射关系:
java->JNI->c/c++

基本数据类型:

java JNI
boolean jboolean
byte jbyte
char jchar
short jshort
int jlong
long jchar
float jfloat
double jdouble
void void

引用类型:

java JNI
String jstring
object jobject
class jclass
byte[] jByteArray
object[] jobjectArray

JNI开发流程的步骤

  1. 编写native方法
  2. javah命令,生成.h头文件
  3. 复制.h头文件到CPP工程中
  4. 复制jni.h和jni_md.h文件到CPP工程中
  5. 创建jni目录
  6. 添加本地支持add native support
  7. 实现.h头文件中声明的函数
  8. 生成动态库Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib
  9. 令执行Java程序,加载动态库

java调用C,例如下面返回一个字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JniTest {
//写一个native方法
public native static String getStringFromC();

public static void main(String[] args) {
String text = getStringFromC();
System.out.println(text);
}
//加载动态库
static{
System.loadLibrary("jni_study");
}

}

C中

1
2
3
4
5
6
7
8
9
//函数实现
//Java_包名_类名_方法名
JNIEXPORT jstring JNICALL Java_com_chs_jni_JniTest_getStringFromC
(JNIEnv *env, jclass jcls){
//JNIEnv 结构体指针 env是二级指针
//代表Java运行环境,调用Java中的代码
//将C的字符串转为一个java字符串
return (*env)->NewStringUTF(env,"C String");
}

C调用java中的方法

访问属性,访问静态属性,访问方法,访问静态方法

先在java中定义相关的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class JniTest {

//加载动态库
static{
System.loadLibrary("jni_test");
}

public String name = "chs";

public static int count = 9;

public native static String getStringFromC();

public native String getString2FromC(int i);
//访问属性,返回修改之后的属性内容
public native String accessField();
//c调用java中的静态变量
public native void accessStaticField();
//c调用java中的方法
public native void accessMethod();
//c调用java中的静态方法
public native void accessStaticMethod();
//c调用java中的构造方法
public native void accessConstructor();
//c调用java中的父类的方法
public native void accessNonvirtualObjectMethod();
//中文
public native String chuneseChars(String in)
//传入数组
public native void giveArray(int[] array);
//获取数组
public native int[] getArray(int len);
//获取本地引用
public native void loaclRef();

public static void main(String[] args) {
String text = getStringFromC();
System.out.println(text);
JniTest t = new JniTest();
text = t.getString2FromC(6);
System.out.println(text);

t.accessField();
t.accessStaticField();
t.accessMethod();
t.accessStaticMethod();
int[] array = {9,100,10,37,5,10};
//排序
t.giveArray(array);
for (int i : array) {
System.out.println(i);
}
int[] array2 = t.getArray(10);
System.out.println("------------");
for (int i : array2) {
System.out.println(i);
}
//全局引用
t.createGlobalRef();
System.out.println(t.getGlobalRef());
//用完之后释放
t.deleteGlobalRef();
System.out.println("释放完了...");
//System.out.println(t.getGlobalRef());
//Java中捕捉C中的异常Exception是无法捕捉到的
//得用Throwable捕获
try {
t.exeception();
} catch (Exception e) {
System.out.println("发生异常:"+e.getMessage());
}
System.out.println("--------异常发生之后-------");
//C中手动抛出来的异常可以捕捉到
try {
t.exeception();
} catch (Exception e) {
//e.printStackTrace();
System.out.println(e.getMessage());
}
//不断调用cached方法
for (int i = 0; i < 100; i++) {
t.cached();
}
}

//产生指定范围的随机数
public int genRandomInt(int max){
return new Random().nextInt(max);
}

//产生UUID字符串
public static String getUUID(){
return UUID.randomUUID().toString();
}

1.访问属性

这里会用到属性签名

数据类型 签名
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
object L开头,然后以/分割包的完整类型,后面再加;
比如String的签名就是Ljava/long/String
Array 以[开头,在加上数组元素类型的签名,
比如int[],签名是[I,int[][]的签名是[[I,
object[]的签名是[Ljava/lang/Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
JNIEXPORT jstring JNICALL Java_com_chs_JniTest_accessField
(JNIEnv *env, jobject jobj){
//jobj是t对象,JniTest.class
jclass cls = (*env)->GetObjectClass(env, jobj);
//jfieldID
//属性名称,属性签名
jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");

//jason >> super jason
//获取key属性的值
//Get<Type>Field
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
printf("jstr:%#x\n",&jstr);

//jstring -> c字符串
//isCopy 是否复制(true代表赋值,false不复制)
char *c_str = (*env)->GetStringUTFChars(env,jstr,JNI_FALSE);
//拼接得到新的字符串
char text[20] = "super ";
strcat(text,c_str);

//c字符串 ->jstring
jstring new_jstr = (*env)->NewStringUTF(env, text);

//修改key
//Set<Type>Field
(*env)->SetObjectField(env, jobj, fid, new_jstr);

printf("new_jstr:%#x\n", &new_jstr);

return new_jstr;
}

2.访问静态属性

1
2
3
4
5
6
7
8
9
10
11
12
13
JNIEXPORT void JNICALL Java_com_chs_JniTest_accessStaticField
(JNIEnv *env, jobject jobj){
//jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//jfieldID
jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
//GetStatic<Type>Field
jint count = (*env)->GetStaticIntField(env, cls, fid);
count++;
//修改
//SetStatic<Type>Field
(*env)->SetStaticIntField(env,cls,fid,count);
}

3.访问java方法

这里需要用到方法的签名,方法的签名的获取,可以通过javap命令,打开命令行,进入我们的java类所在的文件夹,执行javap命令 比如

1
javap -s -p com.chs.JniTest

执行上面的命令,就可以看到该类下面所有属性和方法的签名了。

1
2
3
4
5
6
7
8
9
JNIEXPORT void JNICALL Java_com_chs_JniTest_accessMethod
(JNIEnv *env, jobject jobj){
//jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//jmethodID 最后一个参数是方法的签名
jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
//Call<Type>Method
jint random = (*env)->CallIntMethod(env, jobj, mid, 200);
printf("random num:%ld",random);

4.访问静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JNIEXPORT void JNICALL Java_com_chs_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj){
//jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//jmethodID
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");

//CallStatic<Type>Method
jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);

//随机文件名称 uuid.txt
//jstring -> char*
//第三个参数isCopy JNI_FALSE,代表java和c操作的是同一个字符串
char *uuid_str = (*env)->GetStringUTFChars(env, uuid, NULL);
//拼接
char filename[100];
sprintf(filename, "D://%s.txt",uuid_str);
FILE *fp = fopen(filename,"w");
fputs("i love jason", fp);
fclose(fp);
}

5.访问构造方法

访问构造方式的时候需要传入一个”“参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用java.util.Date产生一个当前的时间戳
JNIEXPORT jobject JNICALL Java_com_chs_JniTest_accessConstructor
(JNIEnv *env, jobject jobj){
//找到要访问的class
jclass cls = (*env)->FindClass(env, "java/util/Date");
//找到它的构造方法
jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
//实例化一个Date对象
jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
//找到想要调用的方法
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
//调用getTime方法
jlong time = (*env)->CallLongMethod(env, date_obj, mid);

printf("\ntime:%lld\n",time);

return date_obj;
}

6.调用父类的方法

Java中定义一个父类Human,和一个子类Man,父类中有个方法sayHai,子类重写该方法。

java中

1
Human human = new Man();

C中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
JNIEXPORT void JNICALL Java_com_chs_JniTest_accessNonvirtualMethod
(JNIEnv *env, jobject jobj){
//拿到该对象
jclass cls = (*env)->GetObjectClass(env, jobj);
//找到属性的ID
jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/chs/Human;");
//获取man属性(对象
jobject human_obj = (*env)->GetObjectField(env, jobj, fid);
//知道父类,注意:传父类的名称
jclass human_cls = (*env)->FindClass(env, "com/chs/Human");
//执行sayHi方法
jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V");
//执行CallObjectMethod,还是会调用子类的方法
//(*env)->CallObjectMethod(env, human_obj, mid);
//使用CallNonvirtualObjectMethod可以调用的父类的方法
(*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);
}

7.从C中返回中文乱码问题

C中字符串默认是UTF-16,想要返回到Java中不乱码,需要进行转码,C中转码非常麻烦,通过C调用Java中的String提供的类来进行转码就简单了很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
JNIEXPORT jstring JNICALL Java_com_chs_JniTest_chineseChars
(JNIEnv *env, jobject jobj, jstring in){
//输出
//char *c_str = (*env)->GetStringUTFChars(env, in, NULL);
//printf("%s\n",c_str);

//c 转化成 jstring
char *c_str = "我是C中的文字";
//char c_str[] = "我是C中的文字";
//jstring jstr = (*env)->NewStringUTF(env, c_str);
//执行String(byte bytes[], String charsetName)构造方法需要的条件
//1.jmethodID
//2.byte数组
//3.字符编码jstring

//找到String类,并回去构造方法的ID
jclass str_cls = (*env)->FindClass(env, "java/lang/String");
jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");

//jbyte 转换成 char
//jbyteArray -> char[]
jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
//byte数组赋值,C中的char跟jbyte类型是一样的
//0->strlen(c_str),从头到尾
//对等于,从c_str这个字符数组,复制到bytes这个字符数组
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);

//字符编码jstring
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

//调用构造函数,返回编码之后的jstring
return (*env)->NewObject(env,str_cls,constructor_mid,bytes,charsetName);
}

8.Java传数组到C中

传入一个数组并排序,操作完一定要同步回去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//比较的方法
int compare(int *a,int *b){
return (*a) - (*b);
}
//传入
JNIEXPORT void JNICALL Java_com_chs_JniTest_giveArray
(JNIEnv *env, jobject jobj, jintArray arr){
//jintArray -> jint指针 -> c int 数组
jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
//printf("%#x,%#x\n", &elems, &arr);

//数组的长度
int len = (*env)->GetArrayLength(env, arr);
//排序
qsort(elems, len, sizeof(jint), compare);

//同步
//0, Java数组进行更新,并且释放C/C++数组
//JNI_ABORT, Java数组不进行更新,但是释放C/C++数组
//JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放)
(*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);
}

9.C返回数组到Java

操作完一定要同步回去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
JNIEXPORT jintArray JNICALL Java_com_chs_JniTest_getArray(JNIEnv *env, jobject jobj, jint len){
//创建一个指定大小的数组
jintArray jint_arr = (*env)->NewIntArray(env, len);
//创建jint数组
jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
int i = 0;
for (; i < len; i++){
elems[i] = i;
}
//同步
(*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);

return jint_arr;
}

10.JNI中的引用

引用类型分为局部引用和全局引用

局部引用可以使用DeleteLocalRef手动释放对象,什么时候释放呢

  1. 当访问一个恨到的java对象,使用完成知乎,还要进行复杂的耗时操作
  2. 当创建了大量的局部引用,占用了太多的内存,这些局部引用只是暂时使用,跟后面的操作没有关联。

局部引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNIEXPORT void JNICALL Java_com_chs_JniTest_localRef(JNIEnv *env, jobject jobj){
int i = 0;
for (; i < 5; i++){
//创建Date对象
jclass cls = (*env)->FindClass(env, "java/util/Date");
jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
jobject obj = (*env)->NewObject(env, cls, constructor_mid);
//省略一些操作

//不在使用jobject对象了
//通知垃圾回收器回收这些对象
(*env)->DeleteLocalRef(env, obj);
//省略一些操作...
}
}

全局引用

全局引用可以共享数据,可以跨多个线程,使用DeleteGlobalRef释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jstring global_str;

//创建全局引用
JNIEXPORT void JNICALL Java_com_chs_JniTest_createGlobalRef(JNIEnv *env, jobject jobj){
jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!");
global_str = (*env)->NewGlobalRef(env, obj);
}

//返回全局引用
JNIEXPORT jstring JNICALL Java_com_chs_JniTest_getGlobalRef(JNIEnv *env, jobject jobj){
return global_str;
}

//释放全局引用
JNIEXPORT void JNICALL Java_com_chs_JniTest_deleteGlobalRef(JNIEnv *env, jobject jobj){
(*env)->DeleteGlobalRef(env, global_str);
}

弱全局引用

全局引用需要手动释放,弱全局引用,跟java中的弱引用差不多,当内存不足的时候系统会释放掉这部分内存。

创建:NewWeakGlobalRef,销毁:DeleteGlobalWeakRef

11.C中异常处理

  • C中发生错误抛出的异常,可以使用Throwable捕获,可以从C中把异常清理后才能继续执行后面的代码。C中使用ExceptionOccurred查看返回的异常是不是NULL。不过不是,可以清空异常,并做一些补救的措施。
  • C中通过手动通过ThrowNew抛出的异常,可以在java层try catch捕获到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
JNIEXPORT void JNICALL Java_com_chs_JniTest_exeception(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");
//检测是否发生Java异常
jthrowable exception = (*env)->ExceptionOccurred(env);
if (exception != NULL){
//让Java代码可以继续运行
//清空异常信息
(*env)->ExceptionClear(env);
//补救措施
fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
}
//获取属性的值
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
char *str = (*env)->GetStringUTFChars(env, jstr, NULL);
//对比属性值是否合法
if (_stricmp(str, "super jason") != 0){
//认为抛出异常,给Java层处理
jclass newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
(*env)->ThrowNew(env,newExcCls,"key's value is invalid!");
}
}

12.缓存策略

如果从java中循环调用很多次下面的方法,我们让对象第一次拿到之后就缓存起来,以后再取的时候就去缓存中取,这时候可以使用局部静态变量来存储。

在实际的项目中,我们也可以在一个方法中一次初始化很多需要的全局的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JNIEXPORT void JNICALL Java_com_chs_JniTest_cached(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
//获取jfieldID只获取一次
//局部静态变量
static jfieldID key_id = NULL;
if (key_id == NULL){
key_id = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
printf("--------GetFieldID-------\n");
}
}

//初始化全局变量,动态库加载完成之后,立刻缓存起来
jfieldID key_fid;
jmethodID random_mid;
JNIEXPORT void JNICALL Java_chs_jni_JniTest_initIds(JNIEnv *env, jclass jcls){
key_fid = (*env)->GetFieldID(env, jcls, "key", "Ljava/lang/String;");
random_mid = (*env)->GetMethodID(env, jcls, "genRandomInt", "(I)I");
}
# c/c++

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×