JNI读取Java成员变量类型对应的签名
C访问Java中对应的成员变量,需要根据变量名和变量签名来定位变量;
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
50public class JniTest {
public String key = "hsh";
private native static String getStringFromC();
// 访问Java中的属性,需要为C提供访问入口
public native String accessField();
public static int count = 0;
// 访问Java中的属性,需要为C提供访问入口
public native void accessStaticField();
public static void main(String[] args) {
// String result = getStringFromC();
// System.out.println(result);
JniTest test = new JniTest();
// System.out.println("before modify:"+test.key);
// test.accessField();
// System.out.println("after modify:"+test.key);
// System.out.println("count = "+count);
// test.accessStaticField();
// System.out.println("count = "+count);
// test.accessMethod();
test.accessStaticMethod();
}
// 访问Java中的方法,需要为C提供访问入口
public native void accessMethod();
public int randomInt() {
return new Random().nextInt();
}
public native void accessStaticMethod();
public static String uuid() {
return UUID.randomUUID().toString();
}
static {
System.out.println( System.getProperty("java.library.path"));
System.loadLibrary("jni/jni_03");
}
}
访问修改Java类成员变量
C可以无视Java的权限修饰符,直接访问不公开权限的成员变量和方法;
1 | /* |
注意:
1 | (*env)->GetStringUTFChars |
类似于这种Getxxx方法,从Java中拷贝对应的对象,其参数3指定是否以拷贝的方式进行转换,
false表示在原来的内存上进行修改,但实际还是复制了一份到C内存中。参数3为true时,
会因为无法成功复制而导致失败,所以默认使用false或者null。1
2
3
4
5
6
7
8
9
10
11
12
13
14/**错误纠正!!!**/
// 此处的参数3传递的是一个jboolean类型的指针,是GetStringUTFChars函数用来
// 告诉调用者,是否已经对字符串进行了复制备份,如果isCopy是JNI_TRUE则表示进行
// 了复制,JNI_FALSE则表示此时操作的和Java层是同一份。
// 也就是说isCopy相当于一个回参,用来告诉调用者,底层的操作情况;
// 注意,默认情况下,不要修改Java层的字符串,所以当为JNI_FALSE,不要对字符串进行修改
// 如果为JNI_TRUE,则注意,必须自己手动释放内存
jboolean isCopy = NULL;
char *c_str = (*env)->GetStringUTFChars(env, jstr, &isCopy);
/**释放字符串!!!**/
// 只要存在回参isCopy的函数,都必须我们手动释放内存!!!
(*env)->ReleaseStringUTFChars(env, jstr, c_str);
在C中操作成功后,需要同步回Java中,所以可以确认,C中是拷贝了一份的。
访问修改Java静态成员变量
1 | JNIEXPORT void JNICALL Java_com_my_jnitest_JniTest_accessStaticField |
C中访问Java中的成员方法
C访问Java中对应的方法,需要根据方法名和方法签名来定位方法;
获取对应的方法签名,可以cd到项目的bin目录下,通过:1
javap -s -p com.xxx.xxx.className
来获取对应的方法签名;1
2
3
4
5
6
7
8
9
10
11JNIEXPORT void JNICALL Java_com_my_jnitest_JniTest_accessMethod
(JNIEnv * env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
// 根据方法名和签名定位方法
jmethodID mid = (*env)->GetMethodID(env, cls, "randomInt", "()I");
// 根据Java中方法的返回值调用对应类型的方法
jint result = (*env)->CallIntMethod(env, jobj, mid);
printf("result : %ld", result);
}
C中访问Java中的静态方法
有些情况下,在C中想实现某个功能是非常麻烦的,比如此处生成的UUID,我们可以通过调用Java
中现成的实现方法,来方便的达到目的;
1 | JNIEXPORT jstring JNICALL Java_com_my_jnitest_JniTest_accessStaticMethod |
访问Java构造方法
通过访问Java类的构造方法,可以在C中创建任意类的对象;
1 | /* |
绕过Java子类方法重写
在C++中,如果要重写父类的方法,需要使用virtual修饰方法,在jni中,可以利用这个特点,绕过
子类对父类相同方法的重写;
java类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Human {
public void sayHi() {
System.out.println("human hi");
}
}
public class Man extends Human {
@Override
public void sayHi() {
System.out.println("man call sayhi");
}
}
当我们在java中使用:
private Human human = new Man();
虽然调用的是human的方法,但实际上是重新指向了Man类重写的方法;
C:
在JNI中,会根据调用CallNonvirtualVoidMethod方法时,传递的jclass类型,调用对应类的方
法,而不会理会Java子类的重写;
1 | /* |
解决Java、C字符串互传的乱码问题
在Java中,字符串一般是UTF-8编码,而在jni中,创建的字符串则是UTF-16编码,所以由C传递
给Java时,会出现乱码问题;
在C中处理字符乱码问题,过程是非常繁琐的,但是采用Java则只需要几行代码即可完成,所以,
我们可以调用Java的api来解决乱码问题;
1 | JNIEXPORT jstring JNICALL Java_com_my_jnitest_JniTest_charSetProblem |
C与Java数组交互
从Java中拷贝对应的数组到C的内存中,操作后,再同步回Java数组中;
C代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17JNIEXPORT jstring JNICALL Java_com_my_jnitest_JniTest_sortArray
(JNIEnv * env, jobject jobj, jintArray arr){
// 获取C的jint数组 (java中的int对应C中long,不能直接用C的int接收)
jint* c_arr = (*env)->GetIntArrayElements(env, arr, JNI_FALSE);
// 获取数组长度
int len = (*env)->GetArrayLength(env, arr);
// 排序
qsort(c_arr, len, sizeof(jint), compare);
// 同步
// 0, 同步Java数组,释放C数组
// 1, JNI_COMMIT 同步Java数组,不释放C数组(函数执行完成会自动释放)
// 2, JNI_ABORT 不同步Java数组,同时释放C数组
(*env)->ReleaseIntArrayElements(env, arr, c_arr, 0);
}
注意:
1 | (*env)->GetIntArrayElements |
类似于这种Getxxx方法,从Java中拷贝对应的对象,其参数3指定是否以拷贝的方式进行转换,
false表示在原来的内存上进行修改,但实际还是复制了一份到C内存中。参数3为true时,
会因为无法成功复制而导致失败,所以默认使用false或者null。
在C中操作成功后,需要同步回Java中,所以可以确认,C中是拷贝了一份的。
_错误纠正:参数三实际是一个jboolean类型的指正变量,将这个指针作为参数三传递进来时,实际是起到
回参的作用,通过这个回参,我们可以知道jni底层是否对操作对象进行了内存拷贝,以此来决定是否能够
修改内容,更重要的是,如果为JNI_TRUE,则注意,必须自己手动释放内存。_
Java代码:1
2
3
4
5
6
7
8public native void sortArray(int[] arr);
int[] arr = new int[]{3,8,1,5,4};
// 调用Native方法
test.sortArray(arr);
for(int a:arr) {
System.out.println(a);
}
向Java返回数组
jni所代表的数组与C中操作的数组分属两个不同的内存中,所以在C中操作完成之后,结果需要
同步进jni数组中,才能够影响到Java层;
1 | // 获取C数组 |