内容简介:JNI(Java Native Interface),它是java中的一套接口,用来跟c和c++通信。java中的数据类型和c的数据类型之间的映射关系:java->JNI->c/c++
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开发流程的步骤
- 编写native方法
- javah命令,生成.h头文件
- 复制.h头文件到CPP工程中
- 复制jni.h和jni_md.h文件到CPP工程中
- 创建jni目录
- 添加本地支持add native support
- 实现.h头文件中声明的函数
- 生成动态库Windows系统下是.dll文件,如果是 Linux 系统下是.so文件,如果是Mac系统下是.jnilib
- 令执行Java程序,加载动态库
java调用C,例如下面返回一个字符串
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中
//函数实现 //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中定义相关的属性和方法
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 |
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.访问静态属性
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命令 比如
javap -s -p com.chs.JniTest
执行上面的命令,就可以看到该类下面所有属性和方法的签名了。
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.访问静态方法
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.访问构造方法
访问构造方式的时候需要传入一个”
//使用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中
Human human = new Man();
C中
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提供的类来进行转码就简单了很多。
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中
传入一个数组并排序,操作完一定要同步回去
//比较的方法 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
操作完一定要同步回去
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手动释放对象,什么时候释放呢
- 当访问一个恨到的java对象,使用完成知乎,还要进行复杂的耗时操作
- 当创建了大量的局部引用,占用了太多的内存,这些局部引用只是暂时使用,跟后面的操作没有关联。
局部引用:
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释放。
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中异常处理
ExceptionOccurred
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中循环调用很多次下面的方法,我们让对象第一次拿到之后就缓存起来,以后再取的时候就去缓存中取,这时候可以使用局部静态变量来存储。
在实际的项目中,我们也可以在一个方法中一次初始化很多需要的全局的变量。
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"); }
以上所述就是小编给大家介绍的《JNI基础知识》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
引爆社群:移动互联网时代的新4C法则(第2版)
唐兴通 / 机械工业出版社 / 69.00元
社群已经被公认为是这个时代的商业新形态,原有的商业逻辑和方法被颠覆,新的基于社群的商业体系和规则亟待构建,今天几乎所有的企业都在为此而努力,都在摸索中前行。 本书提出的“新4C法则”为社群时代的商业践行提供了一套科学的、有效的、闭环的方法论,第1版上市后获得了大量企业和读者的追捧,“新4C法则”在各行各业被大量解读和应用,积累了越来越多的成功案例,被公认为是社群时代通用的方法论。也因此,第1......一起来看看 《引爆社群:移动互联网时代的新4C法则(第2版)》 这本书的介绍吧!