JNI使用手册

3 篇文章 0 订阅

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);
}
Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:jni教程(very very good) 19 Chap6: JNI传递返回值... 26 15.2.2.3 传递字符串... 28 15.2.2.4 传递整型数组... 29 15.2.2.5 传递字符串数组... 30 15.2.2.6 传递对象数组... 31 Chap7:Jni中C++和Java的参数传递... 33 Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组... 47 Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能... 47 Chap9:如何编写jni方法(转载)... 55 1、实例一:在jni中调用标准c中自带的函数printf(): 57 2、实例二、调用c 语言用户定义的函数... 58 3、实例三、在jni函数中访问java类中的对象实例域... 58 4、实例四:在jni函数中访问类的静态实例域... 60 5、实例五:在jni函数中调用java对象的方法... 60 6、实例六:在jni函数中调用java类的静态方法... 61 7、实例七:jni函数中传递基本数据类型参数... 62 8、实例八:在jni函数中传递对象类型参数... 62 9、实例九:在jni函数中处理字符串... 63 10、实例十:在jni函数中处理数组... 64 11、实例十一:在jni中的返回值问题... 65 12、实例十二:在jni中创建java类对象:... 66 Chap10:在 Windows 中实现 Java 本地方法... 66 1.Java 调用 C. 67 2.调试... 76 3.其他信息... 79 Chap11:如何在C/C++中调用Java. 80 1.环境搭建... 81 2.初始化虚拟机... 83 3.访问类方法... 85 4访问类属性... 87 5.访问构造函数... 88 6.数组处理... 89 7.中文处理... 89 8.异常... 91 9.线程和同步访问... 91 10.时间... 92 Chap12:基本JNI调用技术(c/c++与java互调) 93 Chap13:JNI的c代码中,另外一个线程获取 JNIEnv. 96 chap 14:当JNI遇到多线程--java对象如何被C++中的多个线程访问?. 97 chap 15:JNI在多线程中的应用... 101 chap 16:JNI限制(多线程)... 105 chap 17:使用 Java Native Interface 的最佳实践... 106 1.性能缺陷... 107 2.正确性缺陷... 117 3.避免常见缺陷... 121 4.结束语... 128 Chap18:JNI设计实践之路... 129 一、 前言... 129 二、 JNI基础知识简介... 130 三、 Java程序调用非Java程序... 131 四、 C/C++访问Java成员变量和成员方法... 138 五、 异常处理... 140 六、 MFC程序中嵌入Java虚拟机... 142 Chap19:JNI编程系列之基础篇... 148 System.loadLibrary("HelloWorld"); 149 JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 150 Chap20:JNI编程系列之中级篇(上)... 151 1. Java基本类型的传递... 151 2. String参数的传递... 151 3. 数组类型的传递... 153 4. 二维数组和String数组... 154 Chap21:JNI编程系列之高级篇... 155 1. 在一般的Java类中定义native方法... 156 2. 访问Java类的域和方法... 156 3. 在native方法中使用用户定义的类... 157 4. 异常处理... 158
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值