优秀的编程知识分享平台

网站首页 > 技术文章 正文

JNI的使用小记(做个热爱生活的程序媛)

nanyue 2024-08-28 19:08:34 技术文章 6 ℃

使用jni少不了编译c头文件:

  • javac编译java为字节码:
 javac com/test/yp/Test.java  即可拿到字节码
  • 通过javah自动生成头文件:
 javah com.test.yp.Test    //即可自动生成头文件br# jdk高于1.8没有了javah命令,可以对包含native方法的源文件进行如下指令,即可生成头文件:brjavac com/yp/www/MainClient.java -h ./
  • 写cpp实现.h头文件中的方法
  • linux平台上,jni.h位于 /usr/lib/jvm/java-1.8.0-openjdk-armhf/include , jni_md.h位于/usr/lib/jvm/java-1.8.0-openjdk-armhf/include/linux
  • 在linux上编译的时候需要显示的指明jni.h和jni_md.h的位置,如下是编译指令:
  g++ -I /usr/lib/jvm/java-1.8.0-openjdk-armhf/include -I /usr/lib/jvm/java-1.8.0-openjdk-brarmhf/include/linux -o main main.cpp
  • 如何生成.so 库?
 //-shared参数生成动态库br//-fPIC用于生成位置无关代码以供生成动态库使用brg++  -I /usr/lib/jvm/java-1.8.0-openjdk-armhf/include -I /usr/lib/jvm/java-1.8.0-openjdk-armhf/include/linux -shared -fPIC -o libmfirstso.so main.cpp
  • 生成的库放在哪里? 这Linux的环境直接放在java文件路径下找不到,放在System.getProperty("java.library.path") 这个路径下,比如我这里放在/usr/lib下即可。
  • 在链接这个动态库的时候常我遇到的异常:
 //这种情况为路径下没有这个库:brException in thread "main" java.lang.UnsatisfiedLinkError: no testnative in java.library.path: [/usr/java/packages/lib, /usr/lib/aarch64-linux-gnu/jni,br//这种情况多半为实现头文件里面的方法,实现方法的名字写错了,和.h里面不一样:brException in thread "main" java.lang.UnsatisfiedLinkError: 'java.lang.String com.yp.www.JavaNative.testString(java.lang.String)'

各种类型数据的传递是跨平台、跨语言互操作的永恒话题,更复杂的操作其实都可以分解为各种 基本数据类型的操作。只有掌握了基于各种数据类型的互操作,才能称得上掌握了 JNI 开发。

那么下面我们就要开始来探讨c/c++和java通过Jni的数据传输的细节问题:

基本数据类型

 1. int -> jintbr2. boolean -> jbooleanbr3. short -> jshortbr4. float -> jfloatbr5. double -> jdoublebr6. long -> jlong

引用类型:

String引用类型的传输

 # 在jni.h中定义jstring是 :typedef jobject jstring;  为jobject类型br# java中string对应jni中的jstringbrJNIEXPORT jstring JNICALL Java_com_yp_www_JavaNative_testString(JNIEnv *env,jobject object,jstring value)br{br    //得到常量字符指针,指向该字符串的第一个位置br    const char *str = env->GetStringUTFChars(value,0); //1br    //动用字符数组的目的是因为字符指针我们无法直接改变其中的值br    char cap[128];br    //位于cstring头文件中的拷贝函数br    strcpy(cap,str);br    //使用完字符串之后需要进行释放br    env->ReleaseStringUTFChars(value,str); //2br    return env->NewStringUTF(cap); // 3br}

我们基本看到了对String引用类型的操作,进入jni.h看一下还有具体的关于String的方法不多,如下:

 #  //这些方法都对应 一个没有UTF后缀的相同功能方法brbr    //根据字符指针创建jstring返回给java层,相当于返回Stringbr    jstring NewStringUTF(const char *utf) {br        return functions->NewStringUTF(this,utf);br    }br    # 常用的方法br    //根据java层传入的jstring,创建本地使用的字符指针char*br    const char* GetStringUTFChars(jstring str, jboolean *isCopy) {br        return functions->GetStringUTFChars(this,str,isCopy);br    }br    # 常用的方法br    //释放引用br    void ReleaseStringUTFChars(jstring str, const char* chars) {br        functions->ReleaseStringUTFChars(this,str,chars);br    }br    //获取jstring的长度,标识字符的长度br    jsize GetStringUTFLength(jstring str) {br        return functions->GetStringUTFLength(this,str);br    }

native层如何返回一个java层的对象?

  • java对象类型对应native层中的jobject
  • 先创建一个class一会儿用于native层来创建对象
 package com.yp.www;brpublic class Person{br    private String name;br    private int age;br    private boolean sex; // 1: man , 0 :womanbr    public Person(){}br    public Person(String name,int age,boolean sex){br        this.name = name;br        this.age = age;br        this.sex = sex;br    }br    private void doSomeThing(){br        System.out.println(name+":ready to do something..");br    }br    public String toString(){br        System.out.println("Person[name:"+namebr        +"age:"+agebr        +"sex:"+sex+"]");br        return "Persion" + this.hashCode();br    }}br//c/c++ 代码brJNIEXPORT jobject JNICALL Java_com_yp_www_JavaNative_createPerson(JNIEnv* env,jobject object)br{br   //需要创建的是Person类,先获取其jclass, 下面的这个路径如果写成 "Lcom/yp/www/Person"会找不到!br   jclass personClass = env->FindClass("com/yp/www/Person");br   //init 标识构造函数,后面的()V 标识该构造函数的参数和返回参数,可以定位到具体是哪一个构造函数。br   jmethodID personMethod = env->GetMethodID(personClass,"<init>","()V");br   //构建对象,第一个参数是jclass,根据findClass而来,第二个参数是构造函数,第三个参数是构造函数的参数,这里省略。br   jobject personObject = env->NewObject(personClass,personMethod);br   //根据class获取里面的属性br   jfieldID name = env->GetFieldID(personClass,"name","Ljava/lang/String;");br   jfieldID age = env->GetFieldID(personClass,"age","I");br   jfieldID sex = env->GetFieldID(personClass,"sex","Z");br   //设置值br   env->SetObjectField(personObject,name,env->NewStringUTF("贤贤"));br   env->SetIntField(personObject,age,18);br   env->SetBooleanField(personObject,sex,1);br   //返回br   return  personObject;br}
  • 经过上面的一阵操作,我们成功的获取到了来自native层创建的Person对象,同时在native层中设置了参数
  • 涉及到的主要方法:
     # 获取class,    和java中的反射 Class.forName("") 异曲同工之妙br    jclass FindClass(const char *name) {br        return functions->FindClass(this, name);br    }br    # 获取某个方法,此处构建对象,我们用于指定用哪个构造函数br        jmethodID GetMethodID(jclass clazz, const char *name,br                          const char *sig) {br        return functions->GetMethodID(this,clazz,name,sig);br    }br   # 构建对象的几个方法,目的一样,只要是构建对象时构造函数参数不一样,这里提供了不一样的参数的方法br    jobject NewObject(jclass clazz, jmethodID methodID, ...) {br        va_list args;br        jobject result;br        va_start(args, methodID);br        result = functions->NewObjectV(this,clazz,methodID,args);br        va_end(args);br        return result;br    }br    jobject NewObjectV(jclass clazz, jmethodID methodID,br                       va_list args) {br        return functions->NewObjectV(this,clazz,methodID,args);br    }br    jobject NewObjectA(jclass clazz, jmethodID methodID,br                       const jvalue *args) {br        return functions->NewObjectA(this,clazz,methodID,args);br    }br   # 获取属性jfieldIDbr   # 获取class中的属性,第一个参数时jclassbr    jfieldID GetFieldID(jclass clazz, const char *name,br                        const char *sig) {br        return functions->GetFieldID(this,clazz,name,sig);br    }br    //获取一个对象中的另一个对象属性br    jobject GetObjectField(jobject obj, jfieldID fieldID) {br        return functions->GetObjectField(this,obj,fieldID);br    }br    /获取一个对象中的boolean属性br    jboolean GetBooleanField(jobject obj, jfieldID fieldID) {br        return functions->GetBooleanField(this,obj,fieldID);br    }br    # 设置对象中的变量br    void SetObjectField(jobject obj, jfieldID fieldID, jobject val) {br        functions->SetObjectField(this,obj,fieldID,val);br    }br    void SetBooleanField(jobject obj, jfieldID fieldID,br                         jboolean val) {br      void SetIntField(jobject obj, jfieldID fieldID,br                     jint val) {br        functions->SetIntField(this,obj,fieldID,val);br    }      
  • ok,我们讨论下一个话题,native层调用java的方法:
     # Native层调用java的方法,两种:静态和非静态br    //非静态 方法调用br    //获取jobject,可能需要先获取FindClass 拿到jclassbr    jobject = env->NewObject(jclass,methodID,args)br    //获取methodIDbr    jmethodID methodID = env->GetMethodID(jobject,"methodName","()V");br    jnit resultValue = env->CallIntMethod(jobject,method,args);brbr    //静态 方法调用br    //获取静态方法methodIDbr    jmethodID = env->GetStaticMethodID(jclass,"methodName","()V");br    jboolean = env->CallStaticBooleanMethod(jclass,methidID,args);

数组类型

  • 引用类型的数组比如String[],Object[]对应native层的:jobjectArray
  • 基本数据类型的数组:int[] 对应native层的jintArray,long[] 对应jlongArray
  • 注意在jni.h中有如下声明,可见不管什么类型都是jobject类型:
 typedef struct _jobject *jobject;brtypedef jobject jclass;brtypedef jobject jthrowable;brtypedef jobject jstring;brtypedef jobject jarray;brtypedef jarray jbooleanArray;brtypedef jarray jbyteArray;brtypedef jarray jcharArray;brtypedef jarray jshortArray;brtypedef jarray jintArray;brtypedef jarray jlongArray;brtypedef jarray jfloatArray;brtypedef jarray jdoubleArray;brtypedef jarray jobjectArray;

1. 基本数据类型的数组:

 JNIEXPORT jintArray JNICALL Java_com_yp_www_JavaNative_testIntArray(JNIEnv* env,jobject object,jbooleanArray booleanArray)br{br    cout << "#Test the jbooleanArray" << endl;br    //任意基本类型都有对用的Get***ArrayElements()br    jboolean* pBooleanArrays =  env->GetBooleanArrayElements(booleanArray,0);br    jsize size = env->GetArrayLength(booleanArray);br    for(int i = 0;i<size;i++)br    {   //你就说这个移动指针的动作潇洒不潇洒就完了!br        cout << "index: " << i << " value is : " << (int)(*(pBooleanArrays+i)) << endl;br    }brbr   env->ReleaseBooleanArrayElements(booleanArray,pBooleanArrays,0);br    //主动创建一个基本数据类型int的数组br   jintArray mIntArray = env->NewIntArray(10);br   //void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint *buf)br   for(int i =0;i<10;i++)br   {    // 设置值,一样的 有Set***ArrayRegion()的方法来设置值。br      env->SetIntArrayRegion(mIntArray,i,1,&i);br   }br   return mIntArray; //返回br  }

2.引用数据类型的数组

传入String[]对应native层为jobjectArray

 # 可以拿到从java层传入数组的长度brjszie = env->GetArrayLength(jarray)br//根据jsizebrjobject = env->GetObjectArrayElement(jobjectArray array, jsize index)br//jobject可以转成jtringbrjstring = (jstring)jobject; //显示强转brconst char* = env->GetStringUTFChars(jstring,isCopy);br//使用完char之后brenv->ReleaseStringUTFChars

native层返回数组给java对象,不可避免的就是创建java层对象:

 jclass = env->FindClass("com/yp/www/Person")brjmethodID = env->GetMethodID(jclass,"<init>","(Ljava/lang/String;IZ)V");brjobject = env->NewObject(jclass,jmethodID,env->NewStringUTF("干得漂亮"),18,1);//传入构造函数的参数br//最后一个参数传入jobject,就是初始化需要的对象。brjobjectArray = env->NewObjectArray(jsize len, jclass clazz,jobject init)

代码位置:https://github.com/AKJoson/JNIStudy

Tags:

最近发表
标签列表