jni从入门到上手

>>最全面的Java面试大纲及答案解析(建议收藏)  

jni对字符串的处理

jni提供了一种java和c/c++交互的方式

字符串的处理我感觉比较有用的方法有三个

newStringUTF这个用来新建一个java字符串

getStringUTFLength获取java字符串的长度

getStringUTFChars这个用来获取java字符串的指针

releaseStringUTFChars释放获取到的指针

java方法声明:

 public static native String getString();
 public static native void setString(String data);

java方法名和c++方法名的对应,请直接使用Javah生成,不要想自己去写,规则太麻烦了。

c++代码实现

JNIEXPORT jstring JNICALL Java_JNITest_getString(JNIEnv *env, jclass thisObj){
 const char * data ="hello";
 //新建java字符串
 jstring jdata= env->NewStringUTF(data);
 return jdata;
}
JNIEXPORT void JNICALL Java_JNITest_setString(JNIEnv *env, jclass thisObj, jstring data){
 //转换成c++字符串
 const char* showData=env->GetStringUTFChars(data,NULL);
 string msg(showData);
 //释放引用
 env->ReleaseStringUTFChars(data,showData);
 cout<<msg<<endl;
}

jni对一维数组的处理

jni对java一维数组的方法比较实用的有几个

xxx具体用其他数据类型替换

byte对应char

java的char对应c++wchar

NewxxxArray新建java的一维数组

SetxxxArrayRegion用c++数组值赋值给java数组

GetArrayLength获取数组长度

GetIntArrayRegion用java数组值赋值给c++数组

    public static native int[] getIntData();
    public static native void show(int[] data);

我的建议是先把数据转成c++格式,处理完后直接复制给java数组,不要直接对java数组进行数据操作

JNIEXPORT jintArray JNICALL Java_JNITest_getIntData(JNIEnv *env, jclass thisObj){
    int tmpData[7]={1,3,5,6,7,3,4};
    //新建java数组
    jintArray data = env->NewIntArray(7);
    //把c++数组的值传给java数组
    env->SetIntArrayRegion(data,0,7,(jint *)tmpData);
    return data;
}


JNIEXPORT void JNICALL Java_JNITest_show___3I(JNIEnv *env, jclass thisObj, jintArray data){
    //获取数组长度
    int len=env->GetArrayLength(data);
    int* cData=new int[len];
    //把java数组的值赋值给c++数组
    env->GetIntArrayRegion(data,0,len,(jint *)cData);
    for(int i=0;i<len;i++){
        cout<<cData[i];
    }
    delete[] cData;
}

对于获取数组指针,然后操作java数组的方法没有给出,因为效果不如先转换格式,然后按照一种规范操作好用

jni对二维数组的处理

对二维数组的处理是建立在一维数组之上的

我的观念还是优先考虑java数组和c++数组的转换,不直接从java数组中取数据进行操作

FindClass这个方法主要是获取jclass的,所谓的二维数组就是jobjectarray里放着jxxxarray的一维数组,这样理解就好多了,关键是获取到相应的一维数组进行操作

GetSetxxxxArrayElement直接获取某个数组里的元素

 public static native int[][] getData();
 public static native void showArray(int[][] data);
 JNIEXPORT jobjectArray JNICALL Java_JNITest_getData(JNIEnv *env, jclass thisObj){
 int data[2][2]={{1,2},{3,4}};
 //获取数组的class
 jclass intClass = env->FindClass("[I");
 //新建object数组,里面是int[]
 jobjectArray jdata=env->NewObjectArray(2,intClass,NULL);
 //赋值
 for(int i=0;i<2;i++){
  jintArray intdata = env->NewIntArray(2);
  env->SetIntArrayRegion(intdata,0,2,(jint*)&data[i]);
  env->SetObjectArrayElement(jdata,i,intdata);
 }
 return jdata;
}
JNIEXPORT void JNICALL Java_JNITest_showArray(JNIEnv *env, jclass thisObj, jobjectArray array){
 //获取数组长度
 int len = env->GetArrayLength(array);
 int data[10][10];
 for(int i=0;i<len;i++){
  jintArray intdata =(jintArray)env->GetObjectArrayElement(array,i);
  //赋值
  env->GetIntArrayRegion(intdata,0,len,(jint*)&data[i]);
 }
 for(int i=0;i<len;i++){
  for(int j=0;j<len;j++){
   cout<<data[i][j]<<",";
  }
 }
}

这里再介绍几个方法,在特定场合要比先转换来得快

GetxxxArrayElements直接获取数组的指针

releasexxxArrayElemets释放指针

这是一对操作

jni对对象的处理

jni对对象的处理其实比较八股,流程基本都是一样的,这次不列举方法了,常用的都在例子里

首先获取jclass,通过FindClass或者getobjectclass
然后获取方法id或者成员变量id

最后执行方法或者拿到成员变量的值

获取Id的时候有一个问题就是获取签名,我的感觉不要自己去记,直接用javap去查,然后拷贝过来就行

 class Student{
 public static int ID=123;
 private String name;
 public Student(String name){
  this.name=name;
 }
 public void show(){
  System.out.println(name);
 }
}
 JNIEXPORT void JNICALL Java_JNITest_showStudent(JNIEnv *env, jclass thisObj, jobject stuObj){
 //获取jclass
 jclass stuClass =env->FindClass("Student");
 //获取方法id
 jmethodID showID = env->GetMethodID(stuClass,"show","()V");
 //执行方法
 env->CallVoidMethod(stuObj,showID);
 //获取类成员变量id
 jfieldID fieldID =env->GetStaticFieldID(stuClass,"ID","I");
 //获取变量的值
 jint stuID =env->GetStaticIntField(stuClass,fieldID);
 cout<<"ID is "<<stuID<<endl;
 //新建对象
 jmethodID initID = env->GetMethodID(stuClass,"<init>","(Ljava/lang/String;)V");
 jstring name = env->NewStringUTF("second");
 jobject stuNewObj =env->NewObject(stuClass,initID,name);
 env->CallVoidMethod(stuNewObj,showID);
}

异常处理

异常处理其实和java对象处理差不多,你可以选择先new一个异常,然后调用throw方法

也可以通过throwNew这个方法,简单易用,只需要传递jclass和信息就行,推荐大家使用

我感觉写代码还是各自有各自的流程处理,java和c++的交互只在获取值和返回值的时候最好。

类引用只在本地方法返回时有效,不要全局保存。但是java还提供了newGlobalRef来锁定,用完后调用deleteGlobalRef去除引用,但是这在设计上感觉并不是很好的方法,我建议还是需要的时候再获取一个吧。

关于文件

java都是大端,英特尔架构cpu基本是小端,arm架构cpu基本是大端,用c++读取java文件时,只要不是char(这里的char就是java里的byte),必须考虑字节的翻转。

c和c++调用的差异

c++是env->,c是(*env)->;并且后面方法调用要传入env。如下

        (*env)->NewStringUTF(env,xxxx);

具体的例子都在https://git.oschina.net/xpbob/jni.git

我用的环境是mingw,环境变了的话,就修改makefile吧


原文始发于微信公众号(一次编译多处运行):jni从入门到上手