JNI学习笔记:(1)开篇(2)本地代码访问Java代码 (3)本地方法取得Java属性/调用java方法 (4)本地代码创建Java对象(包括javaString) (5) 本地方法处理java数组

来源:互联网 发布:ubuntu开机自启动程序 编辑:程序博客网 时间:2024/05/16 13:57

转自:http://blog.csdn.net/jiben071/article/details/6033613


JNI学习笔记1——开篇

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();          }            }  


(2)使用javah命令生成包含native方法定义的C/C++头文件,

并添加到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  


(3)按照生成的C/C++头文件来写C/C++源文件
    #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;      }  


(4)将C/C++源文件编译成动态链接库(DLL),在debug中找到
(5)把DLL文件加入到PATH环境变量下
(6)Java类中加载DLL,然后调用声明的native方法(注意先

让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程序的时候,尽量少用本地代码


JNI学习笔记2——本地代码访问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访问权限的成员信息



JNI学习笔记3——本地方法取得Java属性/调用java方法

(一)取得Java属性/设定Java属性值

1.取得相应属性的jfieldID之后就可以用

Set<TYPE>Field();Get<TYPE>Field();SetStatic<TYPE>Field();GetStatic<TYPE>Field();


等函数对java属性进行操作b C++源码部分:

 


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);       }            }  

b C++源码部分

    #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);      }  


(二)Java方法的调用

1.取得相应的jmethodID传入函数的参数中,就可以用

Call<TYPE>Method();CallStatic<TYPE>Method();CallNonvittual<TYPE>Method();

等函数对java函数进行调用 


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();       }            }  


b C++源码部分

    #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");       }      }  


(2)新增Child类
    package cn.itcast;            public class Child extends Father {             public void function() {        System.out.println("Child:function");       }          }  


(3)java代码部分
    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();       }      }  


(4)C++源码部分
#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);//调用父类方法    }  

JNI学习笔记4——本地代码创建Java对象(包括javaString)

(一)在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);


(三)在C/C++本地代码中创建JAVA的String字符串对象
1.函数
jstring NewString(const jchar* str,jsize len);
jstring NewStringUTF(const char * str);
//取得字符串的长度
jsize GetStringLength(jstring str);
jsize GetStringUTFLength(jstring str);


JNI学习笔记5——本地方法处理java数组/引用问题/缓存jfieldID/jmethodID

(一)本地方法处理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++全局