JNI学习笔记_Java调用C —— 非Android中使用的方法
一、学习笔记
1.java源码中的JNI函数本机方法声明必须使用native修饰。
2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安全性,会将一些复杂的逻辑和
算法通过本地代码(C或C++)来实现,然后打包成.so动态库文件
3.使用了 JNI 接口的 JAVA 程序,不再像以前那样自由的跨平台。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相
应的动态库。
4.JNI 开发流程主要分为以下 6 步:
(1)编写声明了 native 方法的 Java 类。
(2)将 Java 源代码编译成 class 字节码文件。
(3)用 javah -jni 命令生成.h头文件(-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)。
(4)用本地代码实现.h头文件中的函数。
(5)将本地代码编译成动态库。
(6)拷贝动态库至 java.library.path 本地库搜索目录下,并运行 Java 程序。
5.JVM 查找 native 方法的两种方式:
(1)按照 JNI 规范的命名规则
(2)调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中。
6.Native方法签名中的 JNIEXPORT 和 JNICALL 在 Linux/Unix 系统中,这两个宏可以省略不加。
7.本地实现方法的第二个参数为:如果这个 native 方法是实例方法,则该参数是 jobject,如果是静态方法,则是 jclass。
8.JNI 把 Java 中的所有对象当作一个C指针传递到本地方法中,这个指针指向 JVM 中的内部数据结构,而内部的数据结构在内存中的存储方
式是不可见的。只能从 JNIEnv 指针指向的函数表中选择合适的 JNI 函数来操作 JVM 中的数据结构。
9.JNI 的异常和 Java 中的异常处理流程是不一样的,Java 遇到异常如果没有捕获,程序会立即停止运行。而 JNI 遇到未决的异常不会改变
程序的运行流程,也就是程序会继续往下走。
10.字符串操作
(1)GetStringChars/GetStringUTFChars和ReleaseStringChars/ReleaseStringUTFChars
用于获取和释放以 Unicode/UTF-8 格式编码的字符串。后者是用于释放。
(2)GetStringLength/GetStringUTFLength
由于 UTF-8 编码的字符串以'\0'结尾(也可以使用strlen获取长度),而 Unicode 字符串不是。上面函数获取字符串长度。
(3)GetStringCritical和ReleaseStringCritical
提高 JVM 返回源字符串直接指针的可能性,但是获取和释放之间是不能阻塞的也不能调用其它JNI函数。因为获取这个直接指针后会导致暂停
GC(垃圾回收) 线程,当 GC 被暂停后,如果其它线程触发 GC 继续运行的话,都会导致被阻塞。
(4)GetStringRegion和GetStringUTFRegion
分别表示获取 Unicode 和 UTF-8 编码字符串指定范围内的内容到一个预先分配好的缓冲区中,这个函数不会分配内存,因此也没有释放的函数。
11.JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI 的基本数据类型,可以直
接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选
择合适的 JNI 函数来访问和设置 Java 层的数组对象。
12.GC 会实时扫描所有创建的对象是否还有引用,如果没有引用则会立即清理掉。当我们创建一个像 int 数组对象的时候,当我们在本地代码
想去访问时,发现这个对象正被 GC 线程占用了,这时本地代码会一直处于阻塞状态,直到等待 GC 释放这个对象的锁之后才能继续访问。为了
避免这种现象的发生,JNI 提供了 Get/ReleasePrimitiveArrayCritical 这对函数,本地代码在访问数组对象时会暂停 GC 线程。同样这两个函
数之间不能阻塞或调用其它JNI函数
13.在 JNI 中,只有 jobject 以及子类属于引用变量,会占用引用表的空间,jint,jfloat,jboolean 等都是基本类型变量,不会占用引用
表空间,即不需要释放。引用表最大空间为 512 个,如果超出这个范围,JVM 就会挂掉。
14.本地代码调用 Java 层某个对象的方法或属性,这就是来自 C/C++层本地函数的 callback(回调)。
15.引用类型统一调用CallStaticObjectMethod 函数.(不存在CallStaticStringMethod)。
16.GetMethodID/GetStaticMethodID 和 CallIntMethod/CallStaticIntMethod 和 SetIntField/SetStaticIntField,对应static和非static成员方法的调用函数不同。
17.JNI方法签名的格式为:(形参参数类型列表)返回值。形参参数列表中,引用类型以 L 开头,后面紧跟类的全路径名(需将.全部替换成/),
以分号结尾。
18.GetMethodID()返回的jmethodID应该不是引用,不需要对它调用DeleteLocalRef()。
19.调用 GetMethodID 获取方法 ID 和调用 FindClass 获取 Class 实例后,要做异常判断
20.在 Java 中任何一个类的.class字节码文件被加载到内存中之后,该class子节码文件统一使用 Class 类来表示该类的一个引用(相当于
Java 中所有类的基类是 Object一样)。然后就可以从该类的 Class 引用中动态的获取类中的任意方法和属性。
21.在本地代码中可以调用 JNI 函数可以访问 Java 对象中的非 public(eg private) 属性和方法。
22.由于 ID 对于特定类是相同的,因此只需要查找一次,然后便可重复使用。同样,查找类对象的开销也很大,因此也应该缓存它们。
23.class 和 member id 在一定范围内是稳定的,但在动态加载的 class loader 下,保存全局的 class 要么可能失效,要么可能造成无法卸载classloader。
24.(*env)->FindClass()返回的jclass类型的变量是local referenced的,不能被缓存,因为一次JNI调用返回后它引用的已经回收了,下次在
调用JNI函数时出问题。
25.方法ID和域ID应该是可以缓存的(它不是引用,而且是稳定存在的)。
26.如果在用使用时缓存的 ID,要注意只要本地代码依赖于这个 ID 的值,那么这个类就不会被 unload。另外一方面,如果缓存发生在静态初
始化时,当类被 unload 或 reload 时,ID 会被重新计算。因此,尽量在类静态初始化时就缓存字段 ID、方法 ID。
27.有两种域ID的缓存方法:
静态缓存:在静态代码块中调用JNI native函数缓存。
使用时缓存:就是在正常调用native函数时进行缓存。
28.三种引用介绍
(1)局部引用:
通过 NewLocalRef() 和各种 JNI 接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等)。LocalRef 会阻止 GC 回收所引用
的对象。JNI函数返回后局部引用所引用的对象会被 JVM 自动释放,或在JNI函数中调用 DeleteLocalRef 释放。(*env)->DeleteLocalRef(env,local_ref)
局部引用只有在创建它的本地方法返回前有效,本地方法返回到 Java 层之后,如果 Java 层没有对return的局部引用使用的话,局部引用就会被
JVM 自动释放。在JNI函数中将局部引用存储在静态变量中缓存起来,供下次调用时使用是错误的。
JNI 会将创建的局部引用都存储在一个局部引用表中,如果这个表超过了最大容量限制,就会造成局部引用表溢出,使程序崩溃。经测试,Android 上的
JNI 局部引用表最大数量是 512 个。不用时注意及时调用DeleteLocalRef().
JNI 提供了一系列函数来管理局部引用的生命周期。这些函数包括:EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame、DeleteLocalRef。
局部引用不能跨线程使用,只在创建它的线程有效。不要试图在一个线程中创建局部引用并存储到全局引用中,然后在另外一个线程中使用。
(2)全局引用:
调用 NewGlobalRef() 基于局部引用创建,会阻止 GC 回收所引用的对象。可以跨方法、跨线程使用。JVM 不会自动释放,必须调用 DeleteGlobalRef() 手
动释放。(*env)->DeleteGlobalRef(env, g_cls_string)
只能通过 NewGlobalRef 函数创建全局引用。
(3)弱全局引用:
调用 NewWeakGlobalRef() 基于局部引用或全局引用创建,不会阻止 GC 回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在 JVM 认为应
该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef() 手动释放。(*env)->DeleteWeakGlobalRef(env,g_cls_string)
29.在管理局部引用的生命周期中,Push/PopLocalFrame 是非常方便且安全的。我们可以在本地函数的入口处调用PushLocalFrame,然后在出口处调用
PopLocalFrame,这样的话,在函数内任何位置创建的局部引用都会被释放。而且,这两个函数是非常高效的,强烈建议使用它们。需要注意的是,
如果在函数的入口处调用了PushLocalFrame,记住要在函数所有出口(有 return 语句出现的地方)都要调用 PopLocalFrame。
30.JNI函数签名
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *, jobject); //这两个宏可以忽略
JNIEnv:JNIEnv接口指针指向包含指向函数表的指针的位置。函数表中的每个条目都指向一个JNI函数。本机方法始终通过其中一个JNI函数访问Java虚拟机中的数据结构。
jobject:是对HelloWorld对象本身的引用(有点像C ++中的“this”指针),若是java中的静态native方法,参数二是jclass.
JNIEXPORT和JNICALL宏(在jni.h头文件中定义)确保从本机库导出此函数和C编译器生成具有此函数的正确调用约定的代码。
在/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/jni_md.h中(jni.h中包含它):
#define JNIEXPORT __attribute__((visibility("default")))
#define JNICALL //空宏
31.编译本地方法
$ gcc -fPIC -shared HelloWorld.c -o libHelloWorld.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/
5.运行
$ export LD_LIBRARY_PATH=./ 或 setenv LD_LIBRARY_PATH . 或 不设置环境变量运行$ java -Djava.library.path=. HelloWorld
$ java HelloWorld
32.JNI以不同方式处理原始类型(java中的8种基本类型)和引用类型; 实例方法和静态方法。
33.C和C++使用JNI的区别
从jni.h中关于__cplusplus的条件编译来看,native中的类型和JNIenv中提供的函数都是不同的!
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv; //C++使用这个JNIEnv函数集合,它里面的functions域内嵌了JNINativeInterface_!
#else
typedef const struct JNINativeInterface_ *JNIEnv; //C使用这个JNIEnv函数集合
#endif
二、试验Demo
1.互传基本类型测试
/* hello_world.c */ #include <jni.h> #include <stdio.h> #include "com_study_jnilearn_HelloWorld.h" JNIEXPORT jint JNICALL Java_com_study_jnilearn_HelloWorld_test (JNIEnv *env, jclass cls, jshort s, jint i, jlong l, jfloat f, jdouble d, jchar c, jboolean z, jbyte b) { jint ret = 250; printf("C: s=%hd, i=%d, l=%ld, f=%f, d=%lf, c=%c, z=%d, b=%d\n", s, i, l, f, d, c, z, b); return ret; }
/* HelloWorld.java */ package com.study.jnilearn; public class HelloWorld { public static native int test(short s, int i, long l, float f, double d, char c, boolean z, byte b); public static void main(String[] args) { short s = 1; long l = 20; byte b = 127; int ret; ret = test(s, 1, l, 1.0f, 10.5, 'A', true, b); System.out.println("java: ret=" + ret); } static { System.loadLibrary("hello_world"); } }
编译: $ javac HelloWorld.java -d ./ $ javah -jni com.study.jnilearn.HelloWorld $ gcc -shared -fPIC hello_world.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libhello_world.so 运行: $ java com.study.jnilearn.HelloWorld C: s=1, i=1, l=20, f=1.000000, d=10.500000, c=A, z=1, b=127 java: ret=250
2.互传String类型测试
/* StringTest.java */ package com.study.jnilearn; public class StringTest { public native static String sayHello(String text); public static void main(String[] args) { System.out.println("Java send: Hello, I am Java"); String text = sayHello("Hello, I am Java"); System.out.println("Java get: " + text); } static { System.loadLibrary("string_test"); } }
/* string_test.c */ #include <stdio.h> #include <jni.h> JNIEXPORT jstring JNICALL Java_com_study_jnilearn_StringTest_sayHello (JNIEnv *env, jclass cls, jstring j_str) { const char *str_get = NULL; const char *str_send = "Hello I am C"; jboolean isCopy; //返回JNI_TRUE表示原字符串的拷贝,返回JNI_FALSE表示返回原字符串的指针 str_get = (*env)->GetStringUTFChars(env, j_str, &isCopy); if(str_get == NULL) { return NULL; } printf("C get: %s, isCopy=%d\n", str_get, isCopy); (*env)->ReleaseStringUTFChars(env, j_str, str_get); printf("C send: %s\n", str_send); return (*env)->NewStringUTF(env, str_send); }
编译: $ javac StringTest.java -d ./ $ javah -jni com.study.jnilearn.StringTest $ gcc -shared -fPIC string_test.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libstring_test.so 运行: $ java com.study.jnilearn.StringTest Java send: Hello, I am Java C get: Hello, I am Java, isCopy=1 C send: Hello I am C Java get: Hello I am C
3.基本类型数组类型双向传参
/*IntArray.java*/ class IntArray { private native int[] sortArray(int[] arr); public static void main(String[] args) { IntArray obj = new IntArray(); int arr1[] = new int[10]; for (int i = 0; i < 10; i++) { arr1[i] = i; } int arr2[] = obj.sortArray(arr1); for (int i = 0; i < 10; i++) { System.out.print(arr2[i] + " "); } System.out.println(""); } static { System.loadLibrary("IntArray"); } }
/* IntArray.c */ #include <jni.h> #include <stdio.h> #include <string.h> #include "IntArray.h" void arr_short(jint *arr, jint num) { jint i, j, tmp; for (i = 0; i < num; i++) { for (j = i; j < num; j++) { if (arr[i] < arr[j]) { tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } } } JNIEXPORT jintArray JNICALL Java_IntArray_sortArray (JNIEnv *env, jobject obj, jintArray jarr) { jint *array, length; jint buff[64] = {0}; array = (*env)->GetIntArrayElements(env, jarr, NULL); if (array == NULL) { return 0; /* exception occurred */ } length = (*env)->GetArrayLength(env, jarr); memcpy(buff, array, length * sizeof(jint)); (*env)->ReleaseIntArrayElements(env, jarr, array, 0); /*不能直接传jarr然后返回jarr,因为其是一个local reference, native调用后就被回收了*/ arr_short(buff, length); jintArray new_arr = (*env)->NewIntArray(env, length); (*env)->SetIntArrayRegion(env, new_arr, 0, length, buff); return new_arr; }
编译: $ javac IntArray.java $ javah -jni IntArray $ gcc -shared -fPIC IntArray.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libIntArray.so 运行: $ java IntArray 9 8 7 6 5 4 3 2 1 0
4.JNI函数中回调类的静态方法和实例方法
/* AccessMethod.java */ package com.study.jnilearn; class ClassMethod { private static int javaStaticCallback(String str, int i) { int ret = 1; System.out.format("Java: javaStaticCallback: str=%s " + " i=%d\n", str, i); return ret; } private int javaInstanceCallback(String str, int i) { int ret = 2; System.out.format("Java: javaInstanceCallback: str=%s " + "i=%d\n", str, i); return ret; } } public class AccessMethod { public static native int javaStaticMethod(); public static native int javaInstaceMethod(); public static void main(String[] args) { int ret1, ret2; ret1 = javaStaticMethod(); ret2 = javaInstaceMethod(); System.out.println("ret1=" + ret1 + " ret2=" + ret2); } static { System.loadLibrary("AccessMethod"); } }
/* AccessMethod.c */ #include <stdio.h> #include "com_study_jnilearn_AccessMethod.h" JNIEXPORT jint JNICALL Java_com_study_jnilearn_AccessMethod_javaStaticMethod(JNIEnv *env, jclass cls) { jclass clazz = NULL; jstring str_arg = NULL; jmethodID method_id; jint ret = 0; // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象 clazz =(*env)->FindClass(env, "com/study/jnilearn/ClassMethod"); if (clazz == NULL) { printf("Couldn't find class: com/study/jnilearn/ClassMethod"); return; } // 2、从clazz类中查找javaStaticCallback方法 method_id = (*env)->GetStaticMethodID(env, clazz, "javaStaticCallback", "(Ljava/lang/String;I)I"); if (method_id == NULL) { printf("Couldn't find method: javaStaticCallback"); return; } // 3、调用clazz类的callStaticMethod静态方法 str_arg = (*env)->NewStringUTF(env, "C: str pass to java static method"); ret = (*env)->CallStaticIntMethod(env, clazz, method_id, str_arg, 111); // 4.删除局部引用 (*env)->DeleteLocalRef(env, clazz); (*env)->DeleteLocalRef(env, str_arg); // id is not reference,needn't to delete return ret; } JNIEXPORT jint JNICALL Java_com_study_jnilearn_AccessMethod_javaInstaceMethod(JNIEnv *env, jclass cls) { jclass clazz = NULL; jobject jobj = NULL; jmethodID mid_construct = NULL; jmethodID mid_instance = NULL; jstring str_arg = NULL; jint ret = 0; // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象 clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod"); if (clazz == NULL) { printf("Couldn't find class: com/study/jnilearn/ClassMethod"); return; } // 2、获取类的默认构造方法ID mid_construct = (*env)->GetMethodID(env,clazz, "<init>", "()V"); if (mid_construct == NULL) { printf("Couldn't find construct method"); return; } // 3、查找实例方法的ID mid_instance = (*env)->GetMethodID(env, clazz, "javaInstanceCallback", "(Ljava/lang/String;I)I"); if (mid_instance == NULL) { return; } // 4、创建该类的实例 jobj = (*env)->NewObject(env, clazz, mid_construct); if (jobj == NULL) { printf("Couldn't create new object"); return; } // 5、调用对象的实例方法 str_arg = (*env)->NewStringUTF(env, "C: str pass to java instance method"); ret = (*env)->CallIntMethod(env, jobj, mid_instance, str_arg, 222); // 删除局部引用 (*env)->DeleteLocalRef(env, clazz); (*env)->DeleteLocalRef(env, jobj); (*env)->DeleteLocalRef(env, str_arg); return ret; }
编译: $ javac AccessMethod.java -d ./ $ javah -jni com.study.jnilearn.AccessMethod $ gcc -shared -fPIC AccessMethod.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libAccessMethod.so 运行: $ java com.study.jnilearn.AccessMethod Java: javaStaticCallback: str=C: str pass to java static method i=111 Java: javaInstanceCallback: str=C: str pass to java instance method i=222 ret1=1 ret2=2
5.JNI函数中使用静态和非静态成员属性
/* AccessField.java */ package com.study.jnilearn; class ClassField { private static int num; private String str; public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } public class AccessField { private native static void accessInstanceField(ClassField obj); private native static void accessStaticField(); public static void main(String[] args) { ClassField obj = new ClassField(); obj.setNum(10); obj.setStr("Hello"); // 本地代码访问和修改ClassField为中的静态属性num accessStaticField(); accessInstanceField(obj); System.out.println("Java: ClassField.num = " + obj.getNum()); System.out.println("Java: ClassField.str = " + obj.getStr()); } static { System.loadLibrary("AccessField"); } }
#include "com_study_jnilearn_AccessField.h" JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField (JNIEnv *env, jclass cls, jobject obj) { jclass clazz; jfieldID fid; jstring j_str; jstring j_newStr; const char *c_str = NULL; // 1.获取AccessField类的Class引用 clazz = (*env)->GetObjectClass(env, obj); if (clazz == NULL) { return; } // 2. 获取AccessField类实例变量str的属性ID fid = (*env)->GetFieldID(env, clazz, "str", "Ljava/lang/String;"); if (clazz == NULL) { return; } // 3. 获取实例变量str的值 j_str = (jstring)(*env)->GetObjectField(env, obj, fid); // 4. 将unicode编码的java字符串转换成C风格字符串 c_str = (*env)->GetStringUTFChars(env, j_str, NULL); if (c_str == NULL) { return; } printf("C: ClassField.str = %s\n", c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); // 5. 修改实例变量str的值 j_newStr = (*env)->NewStringUTF(env, "World"); if (j_newStr == NULL) { return; } (*env)->SetObjectField(env, obj, fid, j_newStr); // 6.删除局部引用 (*env)->DeleteLocalRef(env, clazz); (*env)->DeleteLocalRef(env, j_str); (*env)->DeleteLocalRef(env, j_newStr); } JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField (JNIEnv *env, jclass cls) { jclass clazz; jfieldID fid; jint num; // 1.获取ClassField类的Class引用 clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassField"); if (clazz == NULL) { return; } // 2.获取ClassField类静态变量num的属性ID fid = (*env)->GetStaticFieldID(env, clazz, "num", "I"); if (fid == NULL) { return; } // 3.获取静态变量num的值 num = (*env)->GetStaticIntField(env, clazz, fid); printf("C: ClassField.num = %d\n", num); // 4.修改静态变量num的值 (*env)->SetStaticIntField(env, clazz, fid, 88); // 删除属部引用 (*env)->DeleteLocalRef(env, clazz); }
编译: $ javac AccessField.java -d ./ $ javah -jni com.study.jnilearn.AccessField $ gcc -shared -fPIC AccessField.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libAccessField.so 运行: $ java com.study.jnilearn.AccessField C: ClassField.num = 10 C: ClassField.str = Hello Java: ClassField.num = 88 Java: ClassField.str = World
6.优化:类静态初始化缓存
/* AccessCache.java */ public class AccessCache { public static native void initIDs(); public native void nativeMethod(); public void callback() { System.out.println("Java: AccessCache.callback invoked!"); } public static void main(String[] args) { AccessCache accessCache = new AccessCache(); accessCache.nativeMethod(); } static { System.loadLibrary("AccessCache"); initIDs(); } }
#include <stdio.h> #include "AccessCache.h" jmethodID MID_AccessCache_callback; JNIEXPORT void JNICALL Java_AccessCache_initIDs(JNIEnv *env, jclass cls) { printf("initIDs called!\n"); MID_AccessCache_callback = (*env)->GetMethodID(env, cls, "callback", "()V"); } JNIEXPORT void JNICALL Java_AccessCache_nativeMethod(JNIEnv *env, jobject obj) { printf("C: call java's callback()\n"); (*env)->CallVoidMethod(env, obj, MID_AccessCache_callback); }
编译: javac AccessCache.java javah -jni AccessCache $ gcc -shared -fPIC AccessCache.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -o libAccessCache.so 运行: $ java AccessCache initIDs called! C: call java's callback() Java: AccessCache.callback invoked!
jni.pdf翻译总结版:http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/recommend.html
posted on 2019-04-25 15:46 Hello-World3 阅读(337) 评论(0) 编辑 收藏 举报