java JNI(java native interface)

例子介绍

编写相关java类:

PDClss.java

package org.senssic;

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {

		return "名字:" + this.name + "年龄:" + this.age;
	}
}

Sjni.java

package org.senssic;

import java.util.Date;

// 静态属性和静态方法,在本地代码中不需要获取实例即可得到直接或许class就行了
public class Sjni {
	private static String sstr = "静态字段";
	// 基本数据类型
	// 无论是private,protected,public,都能通过jni被本地代码获取,这也破坏了java的封装性
	private String sen = "非静态字段";

	public static String getSstr() {
		return sstr;
	}

	public static void setSstr(String sstr) {
		Sjni.sstr = sstr;
	}

	public String getStr() {
		return sen;
	}

	public void setStr(String str) {
		this.sen = str;
	}

	private int in;
	private byte byt;
	private short shot;
	private long lon;
	private char cha;
	private boolean bool;
	private float floa;
	private double doubl;

	public native void displayParams(int in, byte byt, short shot, long lon,
			char cha, boolean bool, float floa, double boudl);

	public native int add(int a, int b);

	public native void setArray(boolean[] blist);

	public native String[] getSArray();

	public native PDClss getPDC();

	public native void setPDC(PDClss pClss);

	public native PDClss[] getAPDClss();

	public static PDClss sfoo(String name, int age, Date date) {
		PDClss pClss = new PDClss();
		pClss.setName(name);
		pClss.setAge(age);
		System.out.println(pClss.toString() + "时间:" + date.toString());
		return pClss;
	}
}

使用javah生成头文件:生成的头文件位于class所在路径下


添加到lib生成的工程中,并添加jni.h jni_md.h(这两个文件位于jdk的include文件夹中)

因为我使用的不是vc编译器,是gcc的所以,成lib的参数需要修改

第1种方法:
gcc -Wl,--kill-at -shared -o jnihello.dll Native.c
这种方法生成不带@的函数声明

第2种方法:
gcc -Wl,--add-stdcall-alias -shared -o jnihello22.dll Native.c
这种方法会生成2个函数声明,一个是带@的 一个是不带@的。

第3种方法:
在你的本地方法的头文件中中的函数前面加上下划线,比如以前是
JNIEXPORT void JNICALL Java_TestNative_Hello (JNIEnv * , jobject );
现在改成
JNIEXPORT void  JNICALL  _Java_TestNative_Hello (JNIEnv * , jobject );
同时你的实现的cpp文件或者c文件里的函数头也要一致 前面有下划线。

我使用的是第三种,然后建立cpp文件:


别忘了修改org_senssic_Sjni.h中的#include <jni.h>为#include "jni.h",或者你添加到对应的库文件所在路径也行

本地代码的源文件;'

#include<stdio.h>
#include<stdlib.h>
#include "org_senssic_Sjni.h"
/*
 * Class:     org_senssic_Sjni
 * Method:    displayParams
 * Signature: (Ljava/lang/String;IBSJCZFD)V
 */
JNIEXPORT void JNICALL _Java_org_senssic_Sjni_displayParams
  (JNIEnv * env, jobject job, jint ji, jbyte jb, jshort jsh, jlong jl, jchar jc, jboolean jbo, jfloat jf, jdouble jd){
   jclass jc_sjni= env->GetObjectClass(job);
   //操作静态字段
   jfieldID jss_id= env->GetStaticFieldID(jc_sjni,"sstr","Ljava/lang/String;");
   jstring jstr= (jstring)env->GetStaticObjectField(jc_sjni,jss_id);
   const char* str=env->GetStringUTFChars(jstr,0);
   printf("sstr:[%s]\n",str);
   env->DeleteLocalRef(jstr);
   jstr=env->NewStringUTF("static");
   env->SetStaticObjectField(jc_sjni,jss_id,jstr);
   //操作非静态字段
   jfieldID js_id=env->GetFieldID(jc_sjni,"sen","Ljava/lang/String;");
   jstring nstr=(jstring)env->GetObjectField(job,js_id);
   const char * snstr=env->GetStringUTFChars(nstr,0);
   printf("nstr:[%s]\n",snstr);
   env->DeleteLocalRef(nstr);
   nstr=env->NewStringUTF("cstr");
   env->SetObjectField(job,js_id,nstr);

   printf("int:%d\n",ji);
   printf("byte:%x\n",jb);
   printf("short:%hd\n",jsh);
   printf("long:%ld\n",jl);
   printf("char:%hu\n",jc);
   printf("boolean:%s\n",(jbo==JNI_TRUE?"TRUE":"FALSE"));
   printf("float:%f\n",jf);
   printf("double:%lf\n",jd);
  }

/*
 * Class:     org_senssic_Sjni
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL _Java_org_senssic_Sjni_add
  (JNIEnv * env, jobject job, jint jin, jint ji){
      int r=(int)(jin+ji);
      return (jint)r;
  }

/*
 * Class:     org_senssic_Sjni
 * Method:    setArray
 * Signature: ([Z)V
 */
JNIEXPORT void JNICALL _Java_org_senssic_Sjni_setArray
  (JNIEnv * env, jobject job, jbooleanArray jboa){
      jboolean * jbpa=env->GetBooleanArrayElements(jboa,0);
      jsize len=env->GetArrayLength(jboa);
      int i=0;
      for(i;i<len;i+=2){
        jbpa[i]=JNI_FALSE;
        printf("BOOLEAN:%s\n",(jbpa[i]==JNI_TRUE?"TRUE":"FALSE"));
      }
      env->DeleteLocalRef(jboa);
  }

/*
 * Class:     org_senssic_Sjni
 * Method:    getSArray
 * Signature: ()[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL _Java_org_senssic_Sjni_getSArray
  (JNIEnv * env, jobject job){
    jstring str;
    jobjectArray args=0;
    jsize len=5;
    char * sa[]{"fuyang","zhixhou","zhongguo","senssic"};
    int i=0;
    args=env->NewObjectArray(len,env->FindClass("java/lang/String"),0);
    for(i=0;i<len;i++){
      str=env->NewStringUTF(sa[i]);
      env->SetObjectArrayElement(args,i,str);
    }
    return args;
  }

/*
 * Class:     org_senssic_Sjni
 * Method:    getPDC
 * Signature: ()Lorg/senssic/PDClss;
 */
JNIEXPORT jobject JNICALL _Java_org_senssic_Sjni_getPDC
  (JNIEnv * env, jobject job){
     jclass objclass=env->FindClass("org/senssic/PDClss");
     jmethodID p_id=env->GetMethodID(objclass,"<init>","()V");
     jobject pojob=env->NewObject(objclass,p_id);
     jfieldID sname=env->GetFieldID(objclass,"name","Ljava/lang/String;");
     jfieldID sage=env->GetFieldID(objclass,"age","I");
     env->SetObjectField(pojob,sname,env->NewStringUTF("my mane is senssic"));
     env->SetIntField(pojob,sage,21);
     return pojob;
  }
 /*
 * Class:     org_senssic_Sjni
 * Method:    setPDC
 * Signature: (Lorg/senssic/PDClss;)V
 */
JNIEXPORT void JNICALL _Java_org_senssic_Sjni_setPDC
(JNIEnv * env, jobject job, jobject jobdc){
    jclass czpdc=env->GetObjectClass(jobdc);
    jclass czjob=env->GetObjectClass(job);//获取本类类的class对象
    jclass dclazz=env->FindClass("java/util/Date");//取得类的class对象
    jmethodID d_id=env->GetMethodID(dclazz,"<init>","()V");//取得某个构造方法的id
    jobject djob=env->NewObject(dclazz,d_id);//创建对象

    jmethodID sfoo_id=env->GetStaticMethodID(czjob,"sfoo","(Ljava/lang/String;ILjava/util/Date;)Lorg/senssic/PDClss;");
    jstring jstr=env->NewStringUTF("aaaaasenssic");
    env->CallStaticObjectMethod(czjob,sfoo_id,jstr,10,djob);//传入参数

    jmethodID tos_id=env->GetMethodID(czpdc,"toString","()Ljava/lang/String;");
    env->CallObjectMethod(czpdc,tos_id);

}
/*
 * Class:     org_senssic_Sjni
 * Method:    getAPDClss
 * Signature: ()[Lorg/senssic/PDClss;
 */
JNIEXPORT jobjectArray JNICALL _Java_org_senssic_Sjni_getAPDClss
  (JNIEnv * env, jobject job){
     jsize len=5;
     jclass ojbpdc=env->FindClass("org/senssic/PDClss");
     jmethodID sp_id=env->GetMethodID(ojbpdc,"<init>","()V");
     jobject pojob=env->NewObject(ojbpdc,sp_id);
     jobjectArray args=env->NewObjectArray(len,ojbpdc,0);
     jfieldID sname=env->GetFieldID(ojbpdc,"name","Ljava/lang/String;");
     jfieldID sage=env->GetFieldID(ojbpdc,"age","I");
     env->SetObjectField(pojob,sname,env->NewStringUTF("senssic"));
     env->SetIntField(pojob,sage,21);
     for(int i=0;i<len;i++){
            env->SetObjectArrayElement(args,i,pojob);
     }
     return args;
  }

生成lib文件,将生成的lib放入项目路径下,然后运行测试代码

测试代码:

package org.senssic;

public class test {
	public static void main(String[] args) {
		System.loadLibrary("sjnic");
		Sjni sjni = new Sjni();
		sjni.displayParams(10, (byte) 12, (short) 13, 14l, 'a', false, 0.1f,
				1.5);
		System.out.println(sjni.add(10, 30) + Sjni.getSstr() + sjni.getStr());
		boolean[] blist = new boolean[4];
		blist[0] = false;
		blist[1] = true;
		blist[2] = false;
		blist[3] = true;
		sjni.setArray(blist);

		for (String b : sjni.getSArray()) {
			System.out.println(b);
		}
		PDClss pClss = sjni.getPDC();
		System.out.println(pClss);
		sjni.setPDC(pClss);
		PDClss[] pClsses = sjni.getAPDClss();
		for (PDClss pdClss : pClsses) {
			System.out.println(pdClss);
		}
	}
}

运行结果:

40staticcstr
fuyang
zhixhou
zhongguo
senssic
„æ
名字:my mane is senssic年龄:21
名字:aaaaasenssic年龄:10时间:Sat Nov 09 16:35:52 CST 2013
名字:senssic年龄:21
名字:senssic年龄:21
名字:senssic年龄:21
名字:senssic年龄:21
名字:senssic年龄:21
sstr:[静态字段]
nstr:[非静态字段]
int:10
byte:c
short:13
long:14
char:97
boolean:FALSE
float:0.100000
double:1.500000
BOOLEAN:FALSE
BOOLEAN:FALSE

关于签名:

使用javap -s -private 类文件

Java 类型 符号
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
objects对象 Lfully-qualified-class-name;L类名
Arrays数组 [array-type [数组类型
methods方法 (argument-types)return-type(参数类型)返回类型



jni和java的类型对应关系

Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组

有三种形式来调用方法和静态方法和创建对象

Call<Type>Method(jobject obj, jmethodID methodID,...)   
Call<Type>MethodV(jobject obj, jmethodID methodID,va_list args)   
Call<Type>tMethodA(jobject obj, jmethodID methodID,const jvalue *args)   
静态方法:
[java] view plaincopy
CallStatic<Type>Method(jclass clazz, jmethodID methodID,...)   
CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_list args)   
CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,const jvalue *args) 
创建对象
jobject NewObject(jclass clazz, jmethodID methodID, ...)   
jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args)   
jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) 

方式1、Call<Type>Method(jobject obj, jmethodID methodID,...) 
用不定参数的形式来一个个指定对应的参数列表。比如有这么一个Java方法
[java] view plaincopy
public int show(int i,double d,char c){  
     。。。。  
}  
通过这个方法,要这样设置参数列表:
jint i=10L;  
jdouble d=2.4;  
jchar c=L'd';  
env->CallIntMethod(obj,id_show,i,d,c);  

方式2、Call<Type>MethodV(jobject obj, jmethodID methodID,va_list args)
这种方式使用很少。
方式三:Call<Type>MethodA(jobject obj, jmethodID methodID,jvalue* v)
第三种传参的形式是传入一个jvalue的指针。jvalue类型是在 jni.h头文件中定义的联合体union,看它的定义:
typedef union jvalue {  
    jboolean z;  
    jbyte    b;  
    jchar    c;  
    jshort   s;  
    jint     i;  
    jlong    j;  
    jfloat   f;  
    jdouble  d;  
    jobject  l;  
} jvalue;  
联合体可以储存不同类型的变量,但是一个联合体对象只能存储它里面定义的某一个类型的变量。
所以这里要传入一系列参数,要使用jvalue数组或者指针。
例子:
       jvalue * args=new jvalue[3];  
       args[0].i=12L;  
args[1].d=1.2;  
args[2].c=L'c';  
jmethodID id_goo=env->GetMethodID(env->GetObjectClass(obj),"goo","(IDC)V");  
env->CallVoidMethodA(obj,id_goo,args);  
       delete [] args;  //释放内存  
静态方法 的使用也是类似的形式。

jni的引用

从java虚拟机创建的对象传到本地C/C++代码时会产生引用。根据Java的垃圾回收机制,只要有引用存在就不会触发该引用指向的Java对象的垃圾回收。

这些引用在JNI中分为三种:

全局引用(Global Reference)

局部引用(Local Reference)

弱全局引用(Weak Global Reference)since JDK1.2


局部引用

最常见的引用类型就是局部引用,基本上通过JNI返回来的引用都是局部引用。例如使用NewObject就会返回创建出来的实例的局部引用。局部引用只在该native函数中有效,所有在该函数中产生的局部引用,都会在函数返回时候自动释放(freed),也可以使用DeleteLocalRef函数手动释放该引用。

 实际上局部引用的存在,就会防止其指向的对象呗垃圾回收,尤其是当一个局部引用指向一个很庞大的对象,或是在一个循环中生成了局部引用,最好的做法就是在使用完该对象后,或在该循环尾部把这个引用释放掉,以确保在垃圾回收器被触发的时候被回收

在局部引用的有效期中,可以传递到别的本地方法函数中,要强调的是他的有效期仍然只在一次的Java本地函数调用中,所以千万不能用C++全局变量保存他或是把他定义为C++静态局部变量。


全局引用

全局引用可以跨越当前线程,在多个native函数中有效,不过需要编程人员手动来释放该引用。全局引用存在期间会防止在Java的垃圾回收的回收。

与局部引用不同,全局引用的创建不是由JNI自动创建的,全局引用是需要调用NewGlobalRef函数来创建,而释放他需要调用DeleteGlobalRef函数。


弱全局引用

这是JDK1.2新出来的功能,与全局引用相似,创建和释放都需要由编程人员来进行。这种引用于全局引用一样可以在多个本地代码有效,也跨越多线程有效。不一样的是,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象。

使用NewWeakGlobalRef跟DeleteWeakGlobalRef来创建和释放引用


Comparing Reference(比较引用)

有两个对象,用如下方法比较相容性:

env->InSameObject(env,obj1,obj2)

如果相容,返回JNI_TRUE,否则返回JNI_FALSE。


与NULL相比,LocalRef与GlobalRef语义显然,前提是释放了两个引用,程序员重新为相应变量做了NULL初始化。

但对于WeakGlobalRef来说,需要使用下述代码判定;

env->InSameObject(env,wobj,NULL)

因为,对于一个WeakGlobalRef来说可能指向的已经被GC的无效对象

jin的字符串

从java端取得的String属性或者是从方法的String对象返回值,对应在JNI中都是jstring类型,它并不是C/C++中的字符串。jstring类型与C/C++中的字符串类型是不一样的, 所以,我们需要对取得的 jstring类型的字符串进行一系列的转换,才能使用。

JNIEnv提供了一系列的方法来操作字符串。

1、

const jchar *GetStringChars(jstring str, jboolean *isCopy) 

将一个jstring对象,转换为(UTF-16)编码的宽字符串(jchar*)。

const char *GetStringUTFChars(jstring str, jboolean *isCopy)

将一个jstring对象,转换为(UTF-8)编码的字符串(char*)

这两个函数的参数含义:

第一个参数传入一个指向Java 中String对象的jstring变量

第二个参数传入的是一个jboolean的指针。可以使NULL,JNI_TRUE或者JNI_FLASE。如果为JNI_TRUE则表示开辟内存,然后把Java中的String拷贝到这个内存中,然后返回指向这个内存地址的指针。如果为JNI_FALSE,则直接返回指向Java中String的内存指针。这个时候千万不要改变这个内存中的内容,这将破坏String在Java中始终是常量的规则。如果是NULL,则表示不关心是否拷贝字符串,它就不会给jboolean*指向的内存赋值。


使用这两个函数取得的字符,在不适用的时候,要分别对应的使用下面两个函数来释放内存。

RealeaseStringChars(jstring jstr,const jchar* str)

RealeaseStringUTFChars(jstring jstr,const char* str)

第一个参数指定一个jstring变量,即要释放的本地字符串的资源

第二个参数就是要释放的本地字符串

2、

为了增加直接传回指向Java字符串的可能性(而不是拷贝),JDK1.2出来了新的函数GetStringCritical/RealeaseStringCritical

const jchar * GetStringCritical(jstring string, jboolean *isCopy) 
void ReleaseStringCritical(jstring string, const jchar *cstring) 

在GetStringCritical/RealeaseStringCritical之间是一个关键区,在这个关键区中绝对不能呼叫JNI的其他函数,也不能有会造成当前线程中断或是让当前线程等待的任何本地代码。否则将造成关键区代码执行期间垃圾回收器停止运作任何触发垃圾回收器的线程也会暂停,其他的垃圾回收器的线程不能前进直到当前的线程结束而激活垃圾回收器。

并且在关键区中千万不要出现中断操作,或是在JVM中分配任何新对象,否则会造成JVM死锁。

虽说这个函数会增加直接传回指向Java字符串的指针的可能性,不过还是会根据情况传回拷贝的字符串。

没有GetStringUTFCritical函数,因为Java字符串是UTF-16的编码,要转换为UTF-8编码的字符串始终要进行一次拷贝,所以不存在这样的函数。

3、

GetStringRegion  /  GetStringUTFRegion

这也是Java1.2新增的函数,这个函数的动作,是把Java字符串的内容直接拷贝到C/C++的字符数组中。在呼叫这个函数之前必须有一个C/C++分配出来的字符串,然后传入到这个函数中进行字符串的拷贝。

由于C/C++中分配内存开销相对小,而且Java中的String内容拷贝的开销可以忽略,更好的一点事次函数不分配内存,不会抛出OutOfMemoryError

 void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf)     //拷贝java字符串并且UTF-8编码传入buffer

void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) //拷贝java字符串并且UTF-16编码传入buffer

4、

jstring NewString(const jchar *unicode, jsize len)   //根据传入的宽字符串创建一个Java String对象

jstring NewStringUTF(const char *utf)    根据传入的UTF-8字符串创建一个Java String对象

5、

jsize GetStringLength(jstring jstr)   //返回一个java String对象的字符串长度

jsize GetStringUTFLength(jstring jstr) //返回一个java String对象 经过UTF-8编码后的字符串长度


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值