JNI是java与c/c++交互的一种手段,在本文章的基本教程里,使用的是eclipse和vs2017来结合使用jni工程,中级阶段,将介绍jni的基本数据对应的C/c++以及java,高级阶段,将讲解jni自带的一些函数
首先介绍下jni的数据类型
基本数据类型:
java类型 jni类型 c类型 size
…
Boolean Jblloean unsigned char 无符号8位
Byte Jbyte char 有符号8位
Char Jchar unsigned short 无符号16位
Short Jshort short 有符号16位
Int Jint int 有符号32位
Long Jlong long long 有符号64位
Float Jfloat float 32位
Double Jdouble double 64位
引用数据类型
java类型 jni类型
java.lang.Class jclass
java.lang.Throwable jthorwable
java.lang.String jstring
Other objects jobjects
java.lang.Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbooleanArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
Other arrays Jarray
JNI标签
类型 类型标识
boolean Z
byte B
char C
short S
int I
long J
float F
double D
String L/java/lang/String;
int[] [I
object[] [L/java/lang/object;
JNIEnv和jobject
当函数是非静态函数时,jobject是对象实例
当函数是静态函数时,jobject是类实例
接口指针只在当前线程有效,不能把接口指针从一个线程传递到另一个线程.
JNIEnv存储了大量的接口函数,通过他可以实现各种不同的功能,创建新的java线程会调用C/C++代码,会传递新的接口指针
JNI的字符串操作(C++代码)
JNIEXPORT void JNICALL Java_cn_createqy_t1_speak(JNIEnv env, jobject obj,jstring jstr)
{
const char p= env->GetStringUTFChars( jstr, 0);/第一个参数对于函数参数的jstr字符,第二个参数1和0分别代表拷贝或不拷贝字符串序列如果拷贝了,那就不会直接改变java中数组的地址内的值,而是从新在堆或者占上创建/
string str=p;//转为string操作
int len=env->GetStringLength(jstr);//获取字符串长度
jchar jchararray;
env->GetStringRegion(jstr, 0, len,jchararray);/截取就string的str从0开始长为len的字符存放在jchar数组中
len的字符串/
env->ReleaseStringChars(jstr,(jchar)p);//释放函数内部的空间
jstring s=env->NewStringUTF(“sss”);//创建字符串
}
JNI数组操作
以int型数组为例
JNIEXPORT void JNICALL Java_cn_createqy_t1_sss
(JNIEnv * env, jobject obj, jintArray jintarray)
{
jint array=env->GetIntArrayElements(jintarray, 0);//0代表是否拷贝数组
//GetIntArrayElements,这个是线程安全的,当GC扫描到它时,会给它加一把锁
/
也可以使用(int*)env->GetPrimitiveArrayCritical(…,…);获取各种原始类型数组数据,需要注意的是,这个函数返回值为空,调用这个函数时,会暂停其他线程,其他线程不能调用wait或者notify
*/
int len=env->GetArrayLength(jintarray);//获取数组长度
env->ReleaseIntArrayElements(jintarray,array,0);//释放数组
}
jni调用java的实例方法和静态方法
列如;在java的cn.createqy包中,存在一个类
public class t1
{
public native void speak();
public static void say(String str,int i)
{
System.out.println(str+i);
}
public int re(byte b)
{
return 3;
}
}
在项目的bin目录中输入javap -s cn.createqy.t1获得 descriptor方法标签
say和re方法的标签分别是
(B)I
(Ljava/lang/String;I)V
C++中
JNIEXPORT void JNICALL Java_cn_createqy_t1_speak
(JNIEnv * env, jobject)
{
jclass j=env->FindClass("cn/createqy/t1");//寻找类,注意用/分割
//寻找静态方法
jmethodID id=env->GetStaticMethodID(j,"say","(Ljava/lang/String;I)V");/*寻找方法,第一个参数是就class ,也就是上面找的类,第二个参数是方法名,第三个参数是标签,*/
jstring str = env->NewStringUTF("sss");
/*创建一个就string对应参数的string*/
env->CallVoidMethod(j, id, str, 3,...);
//使用返回为void方法,第一个为就jobject(因为这里是静态方法,填jclass就行了),第二个参数为方法名,后面的参数分别是方法需要的参数,如果没有可以不写
//调用实例方法,与调用静态方法有所不同
//寻找实例方法
jmethodID id2=env->GetMethodID(j,"re","(B)Ljava/lang/String;");/*寻找方法,第一个参数是就class ,第二个参数是方法名,第三个参数是标签,*/
//寻找构造函数对象(这里的构造函数是默认无参的)
jmethodID id3=env->GetMethodID(j,"<init>","()V");/*寻找构造函数,第一个参数是就class ,也就是上面找的类,第二个参数是默认构造名,第三个参数是构造标签,*/
//然后我们需要获取这个对象jobject
jobject jo = env->NewObject(j, id3,...);//这里是无参构造,省略号可以忽略
jbyte b = 12;
jint i=env->CallIntMethod(jo, id2,b,...);/*接着,传入一个参数b,也就是第一个参数为上的jobject(这里不能传jclass,因为不是静态方法,但静态方法的call中可以传jobject),第二个参数是方法名,后面参数是要串的构造参数//*/
env.deleteLocalRef(j)/*...删除引用,因为java虚拟机会把引用放在应用表中,如果超过引用表限制,会溢出,我这里只写了一个,后面的jobject等也要删除*/
//调用父类实例方法
package cn.createqy;
class animal
{
public void get()
{
System.out.println("this is father method");
}
public class bird extends animal
{
public native speak();
}
}
//C++中
JNIEXPORT void JNICALL speak(JNIEnv* env,JNIObject)
{
jclass j=env->FindClass("cn/createqy/bird");
//获取子类对象
jmethodID init=env->getMethodID(j,"<init>","()V");
jobject bird=env->newJobject(j,id3,...)//构造中无参数
//然后获取父类
jclass j2=env->findClass("cn/createqy/animal");
//接着获取调用的方法id
jmethodID id1=env->getMethodID(j2,"get","()V");
//最后调用
env->CallNonvirtualBooleanMethod(bird,j2,id1,...);
//第一个参数是子类对象,第二个参数是父类,第三个参数是方法名,后面的参数是所要跟的参数
}
}
获取实例属性(尽量别用,亲测不太好用,非要用的可以用javabean传进去)
package cn.createqy;
public class t1
{
public native speak();
public int a ==2;
public static int b=10;
}
编译后,在C++中
jclass j=env->FindClass("cn/createqy/t1");
//操作实例属性
jfieldID id1=env->getField(j,"a","I");
//第一个参数是指它所在类,可以是jobject或者jclass
//第二个参数是属性的名字
//第三个参数是属性的标签
jint i=env->getIntField(j,id2);
//第一个参数指它所在类
//第二个参数是它的id
env->setIntField(j,id,10);
//这个函数是为了修改其值
//第一个参数是它所在类,第二个参数是属性id
//第三个参数是要修改的值
//操作静态属性,方法与实例属性相似,不过函数名有点不一样
JFieldID id2=env->GetStaticFieldID(j,"i","I");
jint i=env->GetstaticIntField(j,id2);
env->SetstaticIntField(j,id2,12);
jni异常处理
在jni的调用中,如果发生异常,程序不会终止,而是会继续执行,直到程序崩溃,
正确的处理是在每一句可能发生异常的句子后面加上
if(env->ExceptionCheck()) //这个函数会检查是否jni引发异常,如果引发会返回true
{
env->ExceptionCheck();//打印异常的堆栈信息
env->ExceptionClear();//清除引发的异常
env->throwNew(env->findclass("java/lang/Exception),"JNI抛出的异常");
//此时java会自己抛出java.lang.exception:JNI抛出异常
env->FatalError("致命异常");
//这个是致命异常,会终止线程,并退出程序
}
jni动态注册
动态注册与静态注册的不同,在于不需要想静态那样繁琐使用javah编译然后放到c编译器中
举个列子
在java代码中
package cn.createqy;
public class JavaHello
{
public native int sayHello(int i,String str);
}
然后创建一个c文件
//为了方便,先宏定义一下需要注册的类
//实现方法
JNIEXPORT jint JNICALL speak
(JNIEnv *env, jobject obj,jint len,jstring str)
{
return 1;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
//本地注册必须实现的函数
{
JNIEnv* env = NULL;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)//只有在使用了这个之后才能使用env
{
return JNI_ERR;
}
jclass j=env->FindClass("cn/createqy/t1");
//先定义要注册的方法
JNINativeMethod jnm[] =
{
(char*)"speak",(char*)"(ILJava/lang/String;)I",speak
//第一个参数是要注册的方法名
//第二个参数是标签
//第三个参数是对应的本地方法
};
if (0 == env->RegisterNatives(j, jnm, 1))//开始注册,第一个参数是要注册的类,第二个参数是对应的JNIN奥体被Method,第三个参数是要注册的方法个数
{
std::cout << "本地方法注册成功";
}
return JNI_VERSION_1_6;
//返回版本号
}
局部引用,全局引用,弱全局引用
局部引用:
通过NewLocalRef和各种jni接口创建(FindClass,NewObject,GetObjectClass和NewCharArray等)会阻止GC回收所引用对象,不在本函数中跨函数使用过1,不能跨线程使用,函数返回后局部所有用过的对象会被JVM自动施释放,或者调用DeleteLocalRef释放;
env->DeleteLocalRef(local_ref);//释放引用对象local_ref
void TestFuc(JNIEnv* env,jobject obj)
{
jcalss cls_string=env->FindClass("java/lang/String");
jcharArrat elemArray;
static jmethodID cid_string=NULL;
jcharArray charArr=env->NewCharArray(len);
jstring str_obj=env->NewObject(cls_string,cid_string,elemArray);
jstring str_oobj_local_ref=env->NewLocalRef(str_obj);//通过NewLocalRef创建
}
全局引用:调用NewGlobalRef’基于局部引用创建,会阻止GC回收多引用对象.可以跨方法,跨线程,使用;JVM不会自动释放,必须调用DeleteGlobalRef(…);才能释放
static jclass g_cls_string;
void TestFuc(JNIEnv* env,jobject obj)
{
jclass cls_string=env->NewGlobalRef(cls_string);
env->DeleteGlobalRef(cls_string);
}
弱局部引用
调用NewWeakGlobelRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,课跨方法,跨线程使用,引用不会自动释放,在JVM认为应该该回收的时候就会释放;或者调用DeleteWeakGlobal手动释放
static jclass g_cls_string;
void TestFunc(JNIEnv *env,jobject obj)
{
jclass cls_string =env->FindClass("java/lang/String");
g_cls_string=env->NewWeakGlobalRef(cls_string);
}