神武伤害系数:JNI全攻略之三--JNI头文件分析

来源:百度文库 编辑:偶看新闻 时间:2024/05/05 17:50:28

JNI全攻略之三--JNI头文件分析  

2006-12-11 11:21:52|  分类: JNI |字号 订阅

JNI头文件分析

       为了分析JNI头文件,特意写了HeaderTest这个包含多个不同的native方法的测试类。

江苏 无锡 缪小东

1.    测试文件HeaderTest.java

       这是一个相当简单的文件。前两个为成员变量、第三个为非本地方法,主要考察成员变量和非本地方法,是否在头文件中出现。后面一段是几种类型的本地方法,例如无输入无输出的本地方法、输入输出基本数据类型的本地方法、输入输出String类型的本地方法、输入输出java自定义的对象的本地方法、输出java自定义的接口的本地方法、输出自定义的对象的本地方法。后面的就是这几种类型对应的输入输出为数组的方法。最后总和的方法。在此类的外部有一个自定义的辅助类Student。本例中还考察private、friendly、public等访问权限对本地方法产生头文件的影响。以及方法重载对头文件中相应方法的影响……太罗嗦了!大家就看看下面的java文件吧!!!

import java.util.*;

public class HeaderTest{

       private int i = 0 ;

       float f = 0f;

       public void XXX(String s){

              ;

       }     

 

       private native void  doVoid();

       native int doShort();

       native void doArray(Object[] o );

       native int doInt(int i);      //byte ,short ,int,long,float,double ,boolean,char      

       native int doInt(double d);    //byte ,short ,int,long,float,double ,boolean,char

       native int doInt(Object o);    

       native int doInt(double d1,double d2);

       static native int doInt(double d1 ,double d2,double d3);

       static native int doInt(double d1 ,float f,boolean b ,char[] c );  

      

       native int doInt(int[] i);

       native int doInt(int[] i1,double[] i2 );    

       static native int doInt(int[] i1,double[] i2 ,Object[] o );

      

       public native String doString(String s);

       public native Object doObject(Object o );

       public native Enumeration doInterface(Iterator it);

       public native Student doStudent(Student s);

      

 

//     native int[] doInt(int[] i);  //byte ,short ,int,long,float,double ,boolean,char

       public native String[] doString(String[] s);

       public native Object[] doObjects(Object[] o );

       public native Enumeration[] doInterface(Iterator[] it);

       public native Student[] doStudent(Student[] s);

             

       public native static Object doAll(int[] i , String[] s , Student[] student );                       

}

 

class Student{}

二、头文件HeaderTest.h

       编译上面的文件,然后对编译后的类文件,使用javah命令,产生HeaderTest.h头文件。下面就是该头文件的一部分。

 

1.        

2.         #include

3.        

4.         #ifndef _Included_HeaderTest

5.         #define _Included_HeaderTest

6.         #ifdef __cplusplus

7.         extern "C" {

8.         #endif

 

9.        

14.     JNIEXPORT void JNICALL Java_HeaderTest_doVoid (JNIEnv *, jobject);

……

 

1.         #ifdef __cplusplus

2.         }

3.         #endif

4.         #endif

 

       为了阅读的方便,给此头文件加了行号!

三、JNI头文件的组成

       JNI头文件一般由三部分组成:1.头文件;2.本地方法的注册;3.本地方法在头文件中的表示。

1.        1-8 是C、C++的头,它包含了我们在VC工具中要使用的jni.h的头文件,以及其它的一些指令。(相信你对C、C++比较了解吧!不了解可以阅读相关书籍!我这里就不讲解了)

2.        以后的每隔6行就是一个本地方法对应的头文件中的方法。这些方法的注释部分包含Class、Method、和Signature三部分。Class当然就是具有此本地方法的类的名称了,在我们的例子中为HeaderTest;Method就是本地方法的名称了,我们例子中有如doStudent的方法;Signature是方法的标识,它是一个标识符,主要供我们在JNI操作java对象的方法使用的。本节只简单讲述其特征,具体使用见本博客的后续文章。

3.        接着就是一个本地方法在头文件中的表示了!例如:

              JNIEXPORT jobject JNICALL Java_HeaderTest_doObject (JNIEnv *, jobject, jobject);

 

四、分析

1.方法组成分析

       从上面的头文件可以看出:头文件包含以下7个部分:

       JNIEXPORT JNICALL Java_HeaderTest_doVoid (JNIEnv *, jobject);

              1        2       3    4      5       6        7

       我就简单地介绍这7个部分吧!

1)        JNIEXPORT(1)和JNICALL(3)都是JNI的关键字,表示此函数是要被JNI调用的。

2)        (2)为本地方法返回的数据类型,对应与java源文件中的该方法返回的数据类型

3)        Java(4)为JNI中标识此方法来源于java的标识头

4)        _HeaderTest(5)表示该方法所在的类。当该类在包中时前面会按次序加上包名。我将上面的类文件放入名为mxd的包中,相应的5部分就为mxd_HeaderTest了,其它类推吧!

5)        _doVoid(6)为JNI中对应与java文件中本地方法的方法名。这不是完全对应的哦!重载方法是一个例外!

6)        (JNIEnv *, jobject)(7)方法的最后一部分哦!比较复杂,它们有一个共同的特点,包含JNIEnv *――它是一个接口指针,用于定位函数表中的函数!在JNI规范中一般称为“Interface Pointer”。看到这儿好像和过程调用很类似了!是的,JNI中的操作过程,就是面向过程的!后面的jobject是一个指向该类的指针,类似与C语言中的this。这个第二个参数是变化的,当该方法为类的实例方法时该参数为jobject;当该方法为类方法(即静态方法)时该参数为jclass,指向该类的class。详细见下面。

 

2.java的数据类型和JNI中数据类型的映射

       以下是HeaderTest.java的源文件中的本地方法和HeaderTest.h头文件中的方法对照!

       private native void  doVoid();

       native int doInt(int i);      //byte ,short ,int,long,float,double ,boolean,char

       public native String doString(String s);

       public native Object doObject(Object o );

       public native Enumeration doInterface(Iterator it);

       public native Student doStudent(Student s);     

 

       native int[] doInt(int[] i);  //byte ,short ,int,long,float,double ,boolean,char

       public native String[] doString(String[] s);

       public native Object[] doObjects(Object[] o );

       public native Enumeration[] doInterface(Iterator[] it);

       public native Student[] doStudent(Student[] s);

 

JNIEXPORT void JNICALL Java_HeaderTest_doVoid (JNIEnv *, jobject);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__I (JNIEnv *, jobject, jint);

JNIEXPORT jstring JNICALL Java_HeaderTest_doString__Ljava_lang_String_2 (JNIEnv *, jobject, jstring);

JNIEXPORT jobject JNICALL Java_HeaderTest_doObject (JNIEnv *, jobject, jobject);

JNIEXPORT jobject JNICALL Java_HeaderTest_doInterface__Ljava_util_Iterator_2 (JNIEnv *, jobject, jobject);

JNIEXPORT jobject JNICALL Java_HeaderTest_doStudent__LStudent_2 (JNIEnv *, jobject, jobject);

JNIEXPORT jintArray JNICALL Java_HeaderTest_doInt___3I (JNIEnv *, jobject, jintArray);

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doString___3Ljava_lang_String_2 (JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doObjects (JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doInterface___3Ljava_util_Iterator_2(JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doStudent___3LStudent_2 (JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobject JNICALL Java_HeaderTest_doAll (JNIEnv *, jclass, jintArray, jobjectArray, jobjectArray);

 

       从上面的对比可以看到:

1)        java中的返回值void和JNI中的void是完全对应的哦!(仅仅一个而已)。

2)        java中的基本数据类型(byte ,short ,int,long,float,double ,boolean,char-8种)在JNI中对应的数据类型只要在前面加上j就对应了(jbyte ,jshort ,jint,jlong,jfloat,jdouble ,jboolean,jchar)。

3)        java中的对象,包括类库中定义的类、接口以及自定义的类接口,都对应于JNI中的jobject。

4)        java中基本数据类型的数组对应与JNI中的jarray类型。(type就是上面说的8种基本数据类型)

5)        java中对象的数组对应于JNI中的jobjectArray类型。(在java中一切对象、接口以及数组都是对象)

 

       下图是JNI规范中java数据类型和JNI数据类型的映射图。

       第一幅为基本数据类型的映射图:

 

 

       第二幅为引用数据类型的映射图:

 

3.重载方法的区别

       以下是几个没有重载的方法及其在JNI头文件中对应的方法:

       private native void  doVoid();

       native int doShort();

       native void doArray(Object[] o );

 

JNIEXPORT void JNICALL Java_HeaderTest_doVoid (JNIEnv *, jobject);

JNIEXPORT jint JNICALL Java_HeaderTest_doShort (JNIEnv *, jobject);

JNIEXPORT void JNICALL Java_HeaderTest_doArray (JNIEnv *, jobject, jobjectArray);

       从上面的例子可以看出:没有重载的方法在JNI的头文件中对应的方法和java中本地方法的名称是一致的。

 

 

       以下是几个简单的重载方法:

       native int doInt(int i);      //byte ,short ,int,long,float,double ,boolean,char      

       native int doInt(double d);    //byte ,short ,int,long,float,double ,boolean,char

       native int doInt(double d1,double d2);

       static native int doInt(double d1 ,double d2,double d3);

       static native int doInt(double d1 ,float f,boolean b ,char[] c );

       native int doInt(Object o);    

 

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__I (JNIEnv *, jobject, jint);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__D (JNIEnv *, jobject, jdouble);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__DD(JNIEnv *, jobject, jdouble, jdouble);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__DDD (JNIEnv *, jclass, jdouble, jdouble, jdouble);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__DFZ_3C (JNIEnv *, jclass, jdouble, jfloat, jboolean, jcharArray);

JNIEXPORT jint JNICALL Java_HeaderTest_doInt__Ljava_lang_Object_2 (JNIEnv *, jobject, jobject);

       从上面的对比可以看出:重载方法时,方法的后面还有一个表示方法参数的“后缀”。例如上面操作的变量为Int时加一个表示此类型的标识“I”;多个类型当然依次加多个了;当某个变量为数组时在其类型前面会加一个“3”;当其为对象时后面会加一个“L”表示这是一个对象,以及一个该对象的路径,不过路径之间用“_”代替包路径的“.”或者文件路径的“/”哦!这对应于Signature。见后面!

 

 

       下面是复杂的例子:

       public native Student[] doStudent(Student[] s1);

       public native Student[] doStudent(Student[] s1,Student[] s2);

 

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doStudent___3LStudent_2 (JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doStudent___3LStudent_2_3LStudent_2(JNIEnv *, jobject, jobjectArray, jobjectArray);

       从上面的例子可以看出,方法重载时,方法后要依次加上参数名称,前面知道了数组要在参数类型前加“3”,从上面的例子看出:参数为对象时对象名称后还要加上“2”,表示该参数为一个对象。

 

4.实例方法和静态方法的区别

       以下是本地实例方法和本地类方法的比较。红色的为java中的本地方法,蓝色的为JNI头文件中对应的方法。

       public native Student[] doStudent(Student[] s);

       public native static Object doAll(int[] i , String[] s , Student[] student );

 

JNIEXPORT jobjectArray JNICALL Java_HeaderTest_doStudent___3LStudent_2 (JNIEnv *, jobject, jobjectArray);

JNIEXPORT jobject JNICALL Java_HeaderTest_doAll (JNIEnv *, jclass, jintArray, jobjectArray, jobjectArray);

       从上面的比较可以看出:

       在本地实例方法中该参数为jobject,代表对象本身,在本地静态方法中该参数为jclass。这是它们的区别。关于如何操作代表该实例的jobject和操作该类的jclass请关注后续文章。

 

 

 

5.Signature

       在JNI头文件中提到Signature,它主要用于操作Java类中的方法。Signature一般由两部分组成:1.方法参数;2.方法返回值。

1.        方法参数包含在一个括号中,返回值在括号外!

2.        方法参数个数较多时会依次以“;”隔开。

3.        当参数或者返回值是基本数据类型时,必须用其在JNI中的描述符表示。下表就是Java基本数据类型对应的JNI中的描述符。

4.        方法参数或者返回值为java中的对象时,必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则,不在包中时直接“L”加上类名称。

5.        当参数或者返回值为数组时,前面必须加上“[”!

 

       以上就是Signature表示方法的规则哦!看看下面一些Signature能一个个转换为相应的方法吗?

([LStudent;)[LStudent;

([I[Ljava/lang/String;[LStudent;)Ljava/lang/Object;

([LStudent;[LStudent;)[LStudent;

([Ljava/util/Iterator;)[Ljava/util/Enumeration;

([Ljava/lang/Object;)[Ljava/lang/Object;

([Ljava/lang/String;)[Ljava/lang/String;

(LStudent;)LStudent;

       能明白就好了!后续的使用JNI操作Java对象你可以入门了哦!!

6.其它

1.        类中的成员变量、非本地方法在JNI中是不会出现的。即javah产生头文件时只会关心本地方法。

2.        private、friendly、protected以及public这些方法限制符不会在JNI的头文件中出现。这些访问修饰符只有在其它类使用这些方法时有效!JNI中不关心此修饰符!

3.        当我们给HeaderTest.java指定一个包时,不仅头文件中的方法的前面加了包名,并且产生的头文件也变为mxd_HeaderTest.h。