转自:http://blog.csdn.net/jiben071/article/details/6033613
JNI——java native interface
(一)开篇
1.为什么要使用jni
Java有些时候需要调用本地代码(C/C++),jni接口提供了
java与操作系统本地代码互相调用的功能
2.最简单的java调用C/C++代码步骤
(1)首先在java类中声明一个native方法
package cn.itcast; public class TestNative { public native void sayHello(); public static void main(String[] args) { System.loadLibrary("nativeCode");//Java类中加载DLL,然后调用声明的native方法 TestNative tst=new TestNative(); tst.sayHello(); } }
并添加到visualC++ win32 dll工程目录下
(注意加载jdk/include/目录下的jni.h和jni_md.h文件到工程)
#include "jni.h" #ifndef _Included_cn_itcast_TestNative #define _Included_cn_itcast_TestNative #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_cn_itcast_TestNative_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
#include "cn_itcast_TestNative.h" #include<iostream> using namespace std; JNIEXPORT void JNICALL Java_cn_itcast_TestNative_sayHello (JNIEnv *env, jobject obj){ cout<<"Hello!"<<endl; }
让eclipse重新启动以重新读取环境变量)
System.loadLibrary("nativeCode");//Java类中加载DLL,然后调用声明的native方法 TestNative tst=new TestNative(); tst.sayHello();
3.使用jni的两个弊端
(1)使用了jni,那么这个java Application将不能跨平台。如
果要移植到别的平台上,那么native代码就需要重新进行编写
。
(2)Java是强类型语言,而C/C++不是。因此,你必须在写
JNI是更小心
总之,必须在构建Java程序的时候,尽量少用本地代码
(一)本地代码访问Java代码
1.在被调用的C/C++函数中也可以反过来访问java程序中的类
2.javah工具生成的C/C++函数声明中,可以看到两个参数:
JNIEXPORT void JNICALL Java_cn_itcast_sayHello(JNIEnv*
env,jobject obj){
···
}
(二)JNIEnv类型
(1)其实际代表Java环境。通过这个JNIEnv*指针,就可以对Java端
的代码进行操作。例如,创建Java类的对象,调用Java对象的方法,
获取Java对象的属性等等。JNIEnv的指针会被JNI传入到本地方法的
实现函数中来对Java端的代码进行操作。
(2)JNIEnv类中常用函数:
NewObject //创建一个java对象
NewString //创建一个java的String对象
New<TYPE>Array //创建某一个类型的java数组
Get/Set<TYPE>Field //获取或设置某一个对象的属性(需接收参数)
GetStatic/SetStatic<TYPE>Field //获取或设置某一个类的静态属性(需接收表示类的参数)
Call<TYPE>Method //调用某一类的方法
CallStatic<TYPE>Method //调用某一个类的静态方法
(三)Java的数据类型在C/C++中的映射关系
Java类型 本地类型 JNI定义的别名
int long jint/jsize
long _int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double double jdouble
object _jobject* jobject
(四)jclass的取得
1.为了能够在C/C++中使用java类,JNI.h头文件中专门定义了jclass
类型来表是Java中的class类
2.JNIEnv类中取得jclass的函数:
(1)jclass FindClass(const char* clsName);
FindClass会在classpath系统环境变量下寻找类。传入完整类名,注
意包与包之间是用'/'而不是'.'来分隔
如:jclass cls_string=env->FindClass("java/lang/String");
(2)jclass GetObjectClass(jobject obj);
(3)jclass GetSuperClass(jclass obj);
(五)访问java类中的属性与方法
1.JNI在Jni.h头文件中定义了jfieldID,jmethodID类型来分别代表Java
端的属性和方法。
2.在访问或设置java属性时,需先在本地代码取得代表Java属性的
jfieldID;同理java方法亦然
3.获取ID方法(JNIEnv):
(1)GetFieldID/GetMethodID
GetMethodID也能取得构造函数的jmethodID.创建一个java对象时
可以调用指定的构造方法
如:env->GetMethodID(data_Clazz,"<init>","()V");
(2)GetStaticFieldID/GetStaticMethodID
类似java的Reflect,需要指定类跟属性/方法名来取得相应的jfieldID
跟jmethodID
(3)sign是什么
例如TestNative类中有两个重载方法:
package cn.itcast;
public class TestNative{
public void function(int i){
System.out.println("Integer:"+i);
}
public void function(double d){
System.out.println("Double:"+d);
}
}
然后在C/C++代码中需要调用其中一个function方法的话···
//首先取得要调用的方法所在的类···
jclass clazz_TestNative=env->FindClass("cn/itcast/TestNative");
//取得jmethodID之后才能进行调用···
jmethodID id_func=env->GetMethodID
(clazz_TestNative,"function","??");
······
但是到底取得是
void function(int i)还是
void function(double d)的jmethodID呢?
这就是sign的作用了,它用于指定要取得的属性/方法的类型
这里的sign
如果指定为"(I)V"则取回void function(int) 的jmethodID
如果指定为"(D)V"则取回void function(double)的jmethodID
(4)sign签名
用来表示要取得的属性/方法的类型
类型 相应的签名
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
object L用/分隔包的完整类名: Ljava/lang/String;
Array [签名 [I [Ljava/lang/Object;
Method (参数1类型签名 参数2类型签名···)返回值类型签名
(五)使用签名取得属性/方法ID的例子
import java.util.Date;
public class Hello{
public int property;
public int function(int foo,Date date,int[] arr){
System.out.println("function");
return 0;
}
public native void test();
}
//test本地方法的实现:
JNIEXPORT void Java_Hello_test(JNIEnv* env,jobject obj){
//因为test不是静态函数,所以传进来的就是调用这个函数的对象
//否则就传入一个jclass对象表示native()方法所在的类
jclass hello_clazz=env->GetObjectClass(obj);
jfieldID filedID_prop=env->GetFieldID
(hello_calzz,"property","I");
jmethodID methodID_func=env->GetMethodID
(hello_clazz,"function","(I Ljava/util/Date; [I)I");
env->CallIntMethod(obj,methodID_func,OL,NULL,NULL);
}
(六)使用javap命令来产生签名
1.javap -s -p [full class Name]
-s 表示输出签名信息
-p 同-private,输出包括private访问权限的成员信息
(一)取得Java属性/设定Java属性值
1.取得相应属性的jfieldID之后就可以用
Set<TYPE>Field(); Get<TYPE>Field(); SetStatic<TYPE>Field(); GetStatic<TYPE>Field();
2.获取数组属性——>GetObjectField
3.例子:
a java 代码部分:
package cn.itcast; import java.util.Date; public class TestNative { public native void sayHello(); public int number = 10; public static void main(String[] args) { System.loadLibrary("nativeCode");//Java类 中加载DLL,然后调用声明的native方法 TestNative tst=new TestNative(); tst.sayHello(); System.out.println(tst.number); } }
#include "cn_itcast_TestNative.h" #include<iostream> using namespace std; JNIEXPORT void JNICALL Java_cn_itcast_TestNative_sayHello (JNIEnv *env, jobject obj){ // cout<<"Hello!"<<endl; //取得number属性值 jclass clazz_TestNative = env->GetObjectClass(obj); jfieldID id_number = env->GetFieldID (clazz_TestNative,"number","I"); jint number = env->GetIntField(obj,id_number); cout<<number<<endl;//打印属性值 //修改属性值 env->SetIntField(obj,id_number,100L); }
1.取得相应的jmethodID传入函数的参数中,就可以用
Call<TYPE>Method(); CallStatic<TYPE>Method(); CallNonvittual<TYPE>Method();
2.调用形式
java中方法:
boolean function(int i , bouble d , char c){ ··· } //env->CallBooleanMethod(obj , id_function, 100L, 3.44 , L'3');//第一种调用函数形式
//第二种调用形式
jvalue * args = new jvalue[3];//存储参数的数组 args[0].i=100L; args[1].d=3.44; args[2].c=L'3'; env->CallBooleanMethod(obj , id_function , args); delete [] args;//删除内存
3.调用例子
a java代码部分
package cn.itcast; import java.util.Date; public class TestNative { public native void sayHello(); double max(double num1,double num2){ return num1>num2?num1:num2; } public static void main(String[] args) { System.loadLibrary("nativeCode");//Java类 中加载DLL,然后调用声明的native方法 TestNative tst=new TestNative(); tst.sayHello(); } }
#include "cn_itcast_TestNative.h" #include<iostream> using namespace std; JNIEXPORT void JNICALL Java_cn_itcast_TestNative_sayHello (JNIEnv *env, jobject obj){ //取得number属性值 jclass clazz_TestNative = env->GetObjectClass(obj); jmethodID id_max=env->GetMethodID (clazz_TestNative,"max","(DD)D"); jdouble maxvalue=env->CallDoubleMethod(obj, id_max,3.14,3.15); cout<<maxvalue<<endl; }
3.CallNonvirtual<TYPE>Method
如下java代码:
第一段
public class Father{ public void function(){ System.out.println("Father:func"); } }
第二段
public class Child extends Father{ public void function(){ System.out.println("Child:func"); } }
问题:如果出现以下代码,问其是调用哪个方法?
Father p = new Child(); p.function();
如下C++代码:
第一段
class Father{ public: virtual void function(){//若加上virtual又如何 cout<<"Father:func"<<endl; } }
第二段
class Child: public Father public: void function(){ cout<<"Child:func"<<endl; } }
问题:如果出现以下代码,问其是调用哪个成员函数?
Father* p = new Child(); p->function();
在JNI中定义的CallNonvirtual<TYPE>Method就能够对子类对象调
用父类方法的功能。如果想要调用一个对象的父类方法,而不是子类
的这个方法的话,就可以使用CallNonvirtual<TYPE>Method
使用方式:
首先取得父类及要调用的父类方法的jmethodID
然后传入到这个函数就能通过子类对象呼叫被覆写(override)的父
类的方法
使用实例
(1)新增Father类
package cn.itcast; public class Father { public void function(){ System.out.println("Father:function"); } }
package cn.itcast; public class Child extends Father { public void function() { System.out.println("Child:function"); } }
package cn.itcast; import java.util.Date; public class TestNative { public native void sayHello(); public Father p = new Child(); public static void main(String[] args) { System.loadLibrary("nativeCode");//Java类 中加载DLL,然后调用声明的native方法 TestNative tst=new TestNative(); tst.sayHello(); } }
#include "cn_itcast_TestNative.h" #include<iostream> using namespace std; JNIEXPORT void JNICALL Java_cn_itcast_TestNative_sayHello (JNIEnv *env, jobject obj){ jfieldID id_p = env->GetFieldID (clazz_TestNative,"p","Lcn/itcast/Father;"); jobject p = env->GetObjectField(obj,id_p);//取得属性 jclass clazz_Father = env->FindClass ("cn/itcast/Father");//找到Father类 jmethodID id_Father_function = env->GetMethodID (clazz_Father,"function","()V");//获取Father类里面方法的ID //调用方法,取得的是子类方法 env->CallVoidMethod(p,id_Father_function); //相当于如下java代码 //Father p = tst.p; //p.function(); env->CallNonvirtualVoidMethod (p,clazz_Father,id_Father_function);//调用父类方法 }
(一)在C/C++本地代码中创建JAVA对象
1.java对象的创建
(1)函数NewObject可以创建java对象
(2)GetMethodID能够取得构造方法的jmethodID,如果传入的要取得的方法名称设定为“<init>”就能够取得构造方法
(3)构造方法的方法返回值类型的签名始终为Void
(4)例子:
jclass clazz_date = env->FindClass("java/util/Date");
jmethodID mid_date = env->GetMethodID(clazz_date,"<init>","()V");//构造函数ID
jobject now=env->NewObject(clazz_date,mid_date);//创建java对象
(5)另一种方法——> AllocObject(不常用)
(二)在C/C++本地代码中访问JAVA的String字符串对象
(1)在java中,使用的字符串String对象不论是中文还是英文符号,一个字符总是占两个字节
(2)java通过JNI接口可以将java的字符串转换到C/C++中的宽字符串(wchar_t*),或是传回一个UTF-8的字符串(char*)到C/C++.
反过来,C/C++可以通过一个宽字符串,或是一个UTF-8编码的字符串来创建一个java端的String对象
(3)函数
GetStringChars
GetStringUTFChars
这两个函数用来取得某个jstring对象相关的Java字符串。
分别可以取得UTF-16编码的宽字符串(jchar*)跟UTF-8编码的字符串(char*)。
例如:
const jchar* GetStringChars(jstring str,jboolean* copied)
const char* GetStringUTFChars(jstirng str,jboolean* copied)
第一个参数传入一个指向java中的String对象的jstring变量
第二个参数传入一个jboolean的指针
这两个函数分别都会有两个不同的动作:
1.开新内存,然后把java中的string拷贝到这个内存中,然后返回指向
这个内存地址的指针
2.直接返回指向java中String内存的指针,这个时候千万不要改变这个
内存的内容,这将破坏String在java中始终是常量这个原则。
第二个参数是用来标识是否对java的String对象进行了拷贝
如果传入这个jboolean指针不是NULL,则它会给该指针所指向的内存
传入JNI_TRUE或JNI_FALSE标识是否进行了拷贝
传入NULL表示不关心是否拷贝字符串,它就不会给jboolean*指向的内
存赋值
使用这两个函数取得的字符串,在不使用的时候,要使用
ReleaseStringChars/ReleaseStringUTFChars来释放拷贝的内容,或
是释放对java的String对象的引用
ReleaseStringChars(jstring jstr,const jchar * str);
ReleaseStringUTFChars(jstring jstr,const char * str);
第一个参数指定一个jstring变量,即是要释放的本地字符串的来源
第二个参数就是要释放的本地字符串
(4)GetStringCritical
1.为了增加直接传回指向java字符串的指针的可能性(而不是拷贝),
JDK1.2出来了新的函数GetStringCritical/ReleaseStringCritical.
2.在GetStringCritical/ReleaseStringCritical之间是一个关键区,在关键区千万不要出现中断操作,
或是在JVM中分配任何新对象,否则会造成JVM死锁
3.虽说这个函数会增加直接传回指向java字符串指针的可能性,不过还是会根据实际情况传回拷贝过的字符串
4.不支持GetStringUTFCritical
(5)GetStringRegion/GetStringUTFRegion
1.动作:把java字符串的内容直接拷贝到C/C++的字符数组中。在呼叫这个函数之前必须有一个C/C++分配出来的字符串,
然后传入到这个函数中进行字符串的拷贝(此函数不分配内存)
2.例子
//拷贝Java字符串并以UTF-8编码传入buffer
GetStringUTFRegion(jstring str,jsize start,jsize len,char* buffer);
//拷贝java字符串并以UTF-16编码传入buffer
GetStringRegion(jstring str, jsize start,jsize len,jchar* buffer);
(一)本地方法处理java数组
1.数组分两种
(1)基本类型的数组
(2)对象数组(Object[])数组
2.一个通用取数组长度函数:
GetArrayLength(jarray array);
3.处理基本类型数组
(1)Get<TYPE>ArrayElements(<TYPE>Array arr,jboolean* isCopied);//取得数组
Release<TYPE>ArrayElements;//释放数组
(2)Get<TYPE>ArrayRegion(<TYPE>Array arr,jsize start,jsize len,<TYPE>* buffer);//开辟内存,拷贝数组
(3)Set<TYPE>ArrayRegion(<TYPE>Array arr,jsize start, jsize len, const <TYPE>* buffer);//将指定范围的元素用C/C++数组中的元
素赋值
(4)<TYPE>Array New<TYPE>Array(jsize sz)//创建java数组
4.处理对象数组类型
(1)Get/SetObjectArrayElement();//不需释放资源
(二)全局引用/局部引用/弱全局引用
从java虚拟机创建的对象传到本地C/C++代码时会产生引用。根据
Java的垃圾回收机制,只要有引用存在就不会触发该引用指向的Java对
象的垃圾回收
1.局部引用
基本通过JNI返回的引用都为局部引用
例如使用NewObject就会返回创建出来的实力的局部引用。
局部引用只在该native函数中有效,所有在该函数总产生的局部引用,
都会在函数返回的时候自动释放。也可以通过DeleteLocalRef函数手动
释放
2.全局引用
(1)其可以跨越当前线程,在多个native函数中有效,需要手动释放该引用。
(2)其非JNI自动创建
NewGlobalRef();//创建
ReleaseGlobalRef();//释放
3.弱全局引用
(1)可以跨越多线程有效,特点在于此种引用不会阻止垃圾回收器回
收这个引用所指向的对象
(2)函数
NewWeakGlobalRef();//创建
ReleaseWeakGlobalRef();//释放
3.函数
jboolean IsSameObject(jobject obj1,job);//把NULL传入要比较的对
象中,就能判断弱全局引用所指向的java对象是否被回收
(三)缓存jfieldID/jmethodID
1.缓存方式
(1)在用的时候缓存
在native code中使用static局部变量来保存已经查询过的id
(2)在java类初始化时缓存
在java第一次加载这个类的时候首先调用本地代码初始化所有的
jfieldID/jmethodID,当然,这些jfieldID/jmethodID是定义在C/C++全局