使用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