零基础带你吃掉JNI全家桶(三)

前言

以前两篇主要从总体角度讲解了native方法与java方法的通讯以及so文件的做用,一些细节就没有太讲解详细,可能有些朋友对其中有些还不太清晰,本文就从最基本的JNI语法带你们熟悉下,怎么编写native方法,与java方法有哪些区别,二者怎么进行对象传输以及调用。java

零基础带你吃掉JNI全家桶(一)数组

零基础带你吃掉JNI全家桶(二)bash

本文篇幅较长,前面基础知识较多,后面直接开撸代码,请耐心观看~数据结构

一、JNI语法

1.1 JNIEnv 和 jobject是什么?

在native方法中,咱们总会看到这两个参数,好比下面的方法ide

JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
{
cout<<"Hello World"<<endl;
}
复制代码

对于JNIEnv,指代了Java本地接口环境(Java Native Interface Environment),是一个JNI接口指针,指向了本地方法的一个函数表,该函数表中的每个成员指向了一个JNI函数,本地方法经过JNI函数来访问JVM中的数据结构,也就是经过这个JNIEnv* 指针,就能够对Java端的代码进行操做。函数

对于jobject,若是native方法不是static的话,这个obj就表明这个native方法的类实例,若是native方法是static的话,这个obj就表明这个native方法的类的class对象实例,也就是这个方法在哪一个类里面,就表明这个类的对象实例或者class实例post

1.2 JNI数据类型

众所周知,在Java中存在2中数据类型,8种基本数据类型以及引用类型,那么在JNI中也是对应的2种数据类型,引用2张图,具体关系以下:学习

基本数据类型

引用数据类型

基本数据类型都是能够在Native层直接使用的ui

引用数据类型则不能直接使用,须要根据JNI函数进行相应的转换后,才能使用spa

多维数组(包括二维数组)都是引用类型,须要使用 jobjectArray 类型存取其值

1.3 域描述符

基本数据类型基本以特定的大写字母表示

Java类 类型签名
int I
float F
double D
long J
boolean Z
byte B
char C
short S

通常引用类型则为 L + 该类型类描述符 + ; (注意,这儿的分号“;”只得是JNI的一部分,而不是咱们汉语中的分段,下同) 例如:String类型的域描述符为 Ljava/lang/String; 对于数组,其为 : [ + 其类型的域描述符 + ; int[ ] 其描述符为 [I float[ ] 其描述符为 [F String[ ] 其描述符为 [Ljava/lang/String; Object[ ]类型的域描述符为 [Ljava/lang/Object; 多维数组则是 n个[ +该类型的域描述符 , N表明的是几维数组。例如: int [] []其描述符为[[I

1.4 方法操做符

将参数类型的域描述符按照申明顺序放入一堆括号中跟返回值类型的域描述符, 规则以下: (参数的域描述符的叠加)返回类型描述符。 对于没有返回值的, 用V(表示void型)

好比:String test() 对应的就是()Ljava/lang/String; 注意";"不可忘记

​ int f(int i, Object object) 对应就是(ILjava/lang/Object;)I

依次类推,注意要仔细,很容易出错

2. JNI native方法访问 Java

2.1 获取方法和属性id

上面也说过了,引用数据类型是不能直接使用,在native层,你想直接经过java对象操做方法属性不太现实,JNI在jni.h头文件中定义了jfieldID和jmethodID类型来分别表明Java对象的属性和方法。咱们在访问或是设置Java属性的时候,首先就要先在本地代码取得表明该Java属性的jfieldID,而后才能在本地代码进行Java属性操做

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int mAge) {
        age = mAge;
    }

    public String getName() {
        return name;
    }

    public void setName(String mName) {
        name = mName;
    }
}
复制代码

好比这个实体类,好比想要操做setName方法,设置一些值进去

首先获取到这个class对象,熟悉反射的朋友应该一眼就看出来,大体差很少

//获取class对象

jclass clazz_NativeTest=env->FindClass(“com/example/hik/cmake"); //获取methodId //第三个参数就是方法的操做符,参数是String,返回值是空,因此是(Ljava/lang/String;)V jmethodID id_show=env->GetMethodID(clazz_NativeTest,“setName”,"(Ljava/lang/String;)V"); //同理获取filedId也是同样的 jfieldID jfieldID1 = env->GetFieldID(student,"name","Ljava/lang/String;") //下面是调用方法,person是对象实例,相似反射效果 char *c_new_name = "lisi"; jstring str = env->NewStringUTF(c_new_name); env->CallVoidMethod(person, id_show, str); 复制代码

2.2本地建立Java对象

JNIEnv提供了下面几个方法来建立一个Java对象:

jobject NewObject(jclass clazz, jmethodID methodID,...);

jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args);

jobject NewObjectA(jclass clazz, jmethodID methodID,const jvalue *args) ;

本地建立Java对象的函数和前面本地调用Java方法很相似:

第一个参数jclass class 表明的你要建立哪一个类的对象

第二个参数jmethodID methodID 表明你要使用哪一个构造方法ID来建立这个对象。

只要有jclass和jmethodID ,咱们就能够在本地方法建立这个Java类的对象。

指的一提的是:因为Java的构造方法的特色,方法名与类名同样,而且没有返回值,因此对于得到构造方法的ID的方法env->GetMethodID(clazz,method_name ,sig)中的第二个参数是固定为“”,第三个参数和要调用的构造方法有关,默认的Java构造方法没有返回值,没有参数。例如:

jclassclazz=env->FindClass("java/util/Date");                                   
//取得java.util.Date类的jclass对象
jmethodID id_date=env->GetMethodID(clazz,"<init>","()V");    
//取得某一个构造方法的jmethodID
jobject date=env->NewObject(clazz,id_date);                            
 //调用NewObject方法建立java.util.Date对象
复制代码

2.3 实例代码

2.3.1 改变Java对象属性

public class Person {
    private int age;
    private String name;
    public Person() {
    }
    public Person(int mAge, String mName) {
        age = mAge;
        name = mName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int mAge) {
        age = mAge;
    }

    public String getName() {
        return name;
    }

    public void setName(String mName) {
        name = mName;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' + '}'; } } 复制代码

Java层,咱们新建一个实体类Bean,用来操做通讯,而后再增长一个native方法changePersonName

public class NativeHelper  {
    static {
        System.loadLibrary("native-lib");
    }
    public  native String stringFromJNI();
    public  native int add(int a,int b);

    public  native void changePersonName(Person mPerson);

   public native Person getStudent();

   public native List<Person> getPeronList();
}
复制代码

native方法,咱们基于第一篇的基础上,去增长一个方法

//调用java层对象,改变属性
void changeName(JNIEnv *env, jobject instance, jobject person) {
	//获取person的class对象
    jclass student = env->GetObjectClass(person);
    //获取setName方法的id
    jmethodID setNameMethond = env->GetMethodID(student, "setName", "(Ljava/lang/String;)V");
    char *c_new_name = "lisi";
    jstring str = env->NewStringUTF(c_new_name);
    //调用方法,由于返回值是void,因此是CallVoidMethod,再把改变后的str传进去
    env->CallVoidMethod(person, setNameMethond, str);
}
复制代码

记得在动态注册里,把方法添加进去

JNINativeMethod jniNativeMethod[] = {{"stringFromJNI",    "()Ljava/lang/String;",                       (void *) backStringToJava},
                                         {"add",              "(II)I",                                      (void *) addNum},
                                         {"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName}};
复制代码

调用以后,发现name已经被改变成了“lisi”,主界面代码就不贴了,直接调用native方法就行了

2.3.2 返回Java层实体对象

咱们再添加一个方法

public native Person getStudent();
复制代码

native方法,一样的增长一个

//返回java层对象
jobject returnPerson(JNIEnv *env, jobject instance) {
	//获取到person class对象
    jclass jclass1 = env->FindClass("com/example/taolin/jni_project/Person");
    //获取到构造函数的methodId
    jmethodID jmethodID1 = env->GetMethodID(jclass1, "<init>", "(ILjava/lang/String;)V");
    jint age = 20;
    char *back_name = "wangwu";
    jstring str = env->NewStringUTF(back_name);
    //NewObject,根据class对象返回一个实例对象
    jobject perosn = env->NewObject(jclass1, jmethodID1, age, str);
    return perosn;
}
复制代码

动态注册关联一下

JNINativeMethod jniNativeMethod[] = {{"stringFromJNI",    "()Ljava/lang/String;",                       (void *) backStringToJava},
                                         {"add",              "(II)I",                                      (void *) addNum},
                                         {"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName},
                                         {"getStudent",       "()Lcom/example/taolin/jni_project/Person;",  (void *) returnPerson}};
复制代码

主页面直接调用getStudent(),发现返回一个student对象,name为“wangwu”,native层返回对象成成功

2.3.3 native返回list对象给Java

添加一个方法

public native List<Person> getPeronList();
复制代码

来,native层,对应添加

//返回java层一个list
jobject returnList(JNIEnv *env, jobject instance) {
	//由于list是没法实例对象,找到Arraylist,返回class对象
    jclass jclass1 = env->FindClass("java/util/ArrayList");
    //拿到构造函数id
    jmethodID contructMethod = env->GetMethodID(jclass1,"<init>","()V");
    //生成一个Arraylist对象,就是咱们要返回的对象
    jobject list = env->NewObject(jclass1,contructMethod);
    //拿到 list的 add方法的methodId,准备往method添加几个数据
    jmethodID methodAdd = env->GetMethodID(jclass1,"add","(Ljava/lang/Object;)Z");
    //拿到Person的class对象
    jclass studentClass = env->FindClass("com/example/taolin/jni_project/Person");
    //拿到person的构造函数的methodId
    jmethodID jmethodID1 = env->GetMethodID(studentClass, "<init>", "(ILjava/lang/String;)V");
    for(int i =0;i<4;i++){
        jobject person = env->NewObject(studentClass,jmethodID1,i,env->NewStringUTF("tl"));
        //调用 list的add方法,由于返回时boolean值,因此CallBooleanMethod
        env->CallBooleanMethod(list,methodAdd,person);
    }
    return list;
}
复制代码

最后,注册绑定不要忘了

JNINativeMethod jniNativeMethod[] = {{"stringFromJNI",    "()Ljava/lang/String;",                       (void *) backStringToJava},
                                         {"add",              "(II)I",                                      (void *) addNum},
                                         {"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName},
                                         {"getStudent",       "()Lcom/example/taolin/jni_project/Person;",  (void *) returnPerson},
                                         {"getPeronList",     "()Ljava/util/List;",                         (void *) returnList}};
复制代码

主页面调用getPeronList(),能够发现返回list,长度是4,调用成功~

3.总结

JNI学习就暂时告一段落了,由于本人也是刚接触这一块,让我讲的多深,我也是心有力而与不足,由于C++学的也不是太好,因此不敢误人子弟,可是仍是但愿可以帮助到一些准备入门的小伙伴来学习JNI开发。

里面的坑其实仍是挺多的,因此小伙伴必定要本身动手去操做一下,搭一下环境,写一些代码,最后确定是有所收获的,有疑惑或者想法的朋友能够留言讨论,比心!~

相关文章
相关标签/搜索