收藏 分享(赏)

深入浅出JNI.doc

上传人:tkhy51908 文档编号:7814069 上传时间:2019-05-26 格式:DOC 页数:22 大小:446KB
下载 相关 举报
深入浅出JNI.doc_第1页
第1页 / 共22页
深入浅出JNI.doc_第2页
第2页 / 共22页
深入浅出JNI.doc_第3页
第3页 / 共22页
深入浅出JNI.doc_第4页
第4页 / 共22页
深入浅出JNI.doc_第5页
第5页 / 共22页
点击查看更多>>
资源描述

1、深入浅出 JNI JNI 是在学习 Android HAL 时必须要面临一个知识点,如果你不了解它的机制,不了解它的使用方式,你会被本地代码绕的晕头转向,JNI 作为一个中间语言的翻译官在运行Java 代码的 Android 中有着重要的意义,这儿的内容比较多,也是最基本的,如果想彻底了解 JNI 的机制,请查看:http:/ ljeagle 写的 JNI 学习笔记和自己通过 JNI 的手册及 Android 中常用的部分写得本文。JNI 学习笔记:http:/ 概念JNI 是本地语言编程接口。它允许运行在 JVM 中的 Java 代码和用 C、C+或汇编写的本地代码相互操作。在以下几种情况下

2、需要使用到 JNI: 应用程序依赖于特定平台,平台独立的 Java 类库代码不能满足需要 你已经有一个其它语言写的一个库,并且这个库需要通过 JNI 来访问 Java 代码 需要执行速度要求的代码实现功能,比如低级的汇编代码通过 JNI 编程,你可以使用本地方法来:l 创建、访问、更新 Java 对象l 调用 Java 方法l 捕获及抛出异常l 加载并获得类信息l 执行运行时类型检查JNI 的原理JVM 将 JNI 接口指针传递给本地方法,本地方法只能在当前线程中访问该接口指针,不能将接口指针传递给其它线程使用。在 VM 中 JNI 接口指针指向的区域用来分配和存储线程本地数据。当 Java

3、代码调用本地方法时,VM 将 JNI 接口指针作为参数传递给本地方法,当同一个 Java 线程调用本地方法时 VM 保证传递给本地方法的参数是相同的。不过,不同的Java 线程调用本地方法时,本地方法接收到的 JNI 接口指针是不同的。加载和链接本地方法在 Java 里通过 System.loadLibrary()来加载动态库,但是,动态库只能被加载一次,因此,通常动态库的加载放在静态初始化语句块中。cpp view plaincopyprint?1. package pkg; 2. class Cls 3. native double f(int i, String s); / 声明为本地方

4、法 4. static 5. System.loadLibrary(“pkg_Cls”); / 通过静态初始化语句块来加载动态库 6. 7. 通常在动态库中声明大量的函数,这些函数被 Java 调用,这些本地函数由 VM 维护在一张函数指针数组中,在本地方法里通过调用 JNI 方法 RegisterNatives()来注册本地方法和 Java 方法的映射关系。本地方法可以由 C 或 C+来实现,C 语言版本:cpp view plaincopyprint?1. jdouble native_fun ( 2. JNIEnv *env, /* interface pointer */ 3. job

5、ject obj, /* “this“ pointer */ 4. jint i, /* argument #1 */ 5. jstring s) /* argument #2 */ 6. 7. /* Obtain a C-copy of the Java string */ 8. const char *str = (*env)-GetStringUTFChars(env, s, 0); 9. 10. /* process the string */ 11. . 12. 13. /* Now we are done with str */ 14. (*env)-ReleaseStringUT

6、FChars(env, s, str); 15. return . 16. C+语言版本:cpp view plaincopyprint?1. extern “C“ /* specify the C calling convention */2. jdouble native_fun ( 3. JNIEnv *env, /* interface pointer */ 4. jobject obj, /* “this“ pointer */ 5. jint i, /* argument #1 */ 6. jstring s) /* argument #2 */ 7. 8. const char

7、*str = env-GetStringUTFChars(s, 0); 9. . 10. env-ReleaseStringUTFChars(s, str); 11. return . 12. 由上面两段代码对比可知,本地代码使用 C+来实现更简洁。两段本地代码第一个参数都是 JNIEnv*env,它代表了 VM 里的环境,本地代码可以通过这个 env 指针对 Java 代码进行操作,例如:创建 Java 类对象,调用 Java 对象方法,获取 Java 对象属性等。jobject obj 相当于 Java 中的 Object 类型,它代表调用这个本地方法的对象,例如:如果有 new Nati

8、veTest.CallNative(),CallNative()是本地方法,本地方法第二个参数是 jobject 表示的是 NativeTest 类的对象的本地引用。如果本地方法声明为 static 类型cpp view plaincopyprint?1. static jint native_get_count(JNIEnv* env, jobject thiz); 数据传递l 基本类型用 Java 代码调用 CC+代码时候,肯定会有参数数据的传递。两者属于不同的编程语言,在数据类型上有很多差别,应该要知道他们彼此之间的对应类型。例如,尽管 C 拥有 int 和 long 的数据类型,但是他

9、们的实现却是取决于具体的平台。在一些平台上, int类型是 16 位的,而在另外一些平台上市 32 位的整数。基于这个原因,Java 本地接口定义了 jint,jlong 等等。Java Language Type JNI Typeboolean jboolean由 Java 类型和 C/C+数据类型的对应关系,可以看到,这些新定义的类型名称和 Java类型名称具有一致性,只是在前面加了个 j,如 int 对应 jint,long 对应 jlong。我们看看 jni.h 和 jni_md.h 头文件,可以更直观的了解:cpp view plaincopyprint?1. typedef uns

10、igned char jboolean; 2. typedef unsigned short jchar; 3. typedef short jshort; 4. typedef float jfloat; 5. typedef double jdouble; 6. typedef long jint; 7. typedef _int64 jlong; 8. typedef signed char jbyte; 由 jni 头文件可以看出,jint 对应的是 C/C+中的 long 类型,即 32 位整数,而不是C/C+中的 int 类型(C/C+中的 int 类型长度依赖于平台),它和 Ja

11、va 中 int 类型一样。所以如果要在本地方法中要定义一个 jint 类型的数据,规范的写法应该是 jint i=123L;再比如 jchar 代表的是 Java 类型的 char 类型,实际上在 C/C+中却是 unsigned short类型,因为 Java 中的 char 类型为两个字节。而在 C/C+中有这样的定义:typedef unsigned short wchar_t。所以 jchar 就是相当于 C/C+中的宽字符。所以如果要在本地方法中要定义一个 jchar 类型的数据,规范的写法应该是 jchar c=LC;实际上,所有带 j 的类型,都是代表 Java 中的类型,并且

12、 jni 中的类型接口与本地代码在类型大小是完全匹配的,而在语言层次却不一定相同。在本地方法中与 JNI 接口调用时,要在内部都要转换,我们在使用的时候也需要小心。l Java 对象类型Java 对象在 CC+代码中的形式如下:cpp view plaincopyprint?1. class _jclass : public _jobject ; 2. class _jthrowable : public _jobject ; 3. class _jstring : public _jobject ; 4. class _jarray : public _jobject ; 5. class

13、_jbooleanArray : public _jarray ; 6. class _jbyteArray : public _jarray ; byte jbytechar jcharshort jshortint jintlong jlongfloat jfloatdouble jdouble All Reference type jobject7. class _jcharArray : public _jarray ; 8. class _jshortArray : public _jarray ; 9. class _jintArray : public _jarray ; 10.

14、class _jlongArray : public _jarray ; 11.class _jfloatArray : public _jarray ; 12.class _jdoubleArray : public _jarray ; 13.class _jobjectArray : public _jarray ; 所有的_j 开头的类,都是继承于_jobject,这也是 Java 语言的特别,所有的类都是Object 的子类,这些类就是和 Java 中的类一一对应,只不过名字稍有不同而已。1) jclass 类和如何取得 jclass 对象在 Java 中,Class 类型代表一个 J

15、ava 类编译的字节码,即:这个 Java 类,里面包含了这个类的所有信息。在 JNI 中,同样定义了这样一个类:jclass。了解反射的人都知道 Class 类是如何重要,可以通过反射获得 java 类的信息和访问里面的方法和成员变量。JNIEnv 有几个方法可以取得 jclass 对象:cpp view plaincopyprint?1. jclass FindClass(const char *name) 2. return functions-FindClass(this, name); 3. FindClass 会在系统 classpath 环境变量下寻找 name 类,注意包的间隔

16、使用 “/ “,而不是”. “,如:cpp view plaincopyprint?1. jclass cls_string=env-FindClass(“java/lang/String“); 获得对象对应的 jclass 类型:cpp view plaincopyprint?1. jclass GetObjectClass(jobject obj) 2. return functions-GetObjectClass(this,obj); 3. 获得一个类的父类 jclass 类型:cpp view plaincopyprint?1. jclass GetSuperclass(jclass

17、 sub) 2. return functions-GetSuperclass(this,sub); 3. JNI 本地方法访问 Java 属性和方法在 JNI 调用中,不仅仅 Java 可以调用本地方法,本地代码也可以调用 Java 中的方法和成员变量。在 Java1.0 中“原始的 ”Java 到 C 的绑定中,程序员可以直接访问对象数据域。然而,直接方法要求虚拟机暴露他们的内部数据布局,基于这个原因,JNI 要求程序员通过特殊的 JNI 函数来获取和设置数据以及调用 java 方法。1) 取得代表属性和方法的 jfieldID 和 jmethodID为了在 C/C+中表示属性和方法, J

18、NI 在 jni.h 头文件中定义了 jfieldID 和 jmethodID 类型来分别代表 Java 对象的属性和方法。我们在访问或是设置 Java 属性的时候,首先就要先在本地代码取得代表该 Java 属性的 jfieldID,然后才能在本地代码进行 Java 属性操作。同样的,我们需要调用 Java 对象方法时,也是需要取得代表该方法的jmethodID 才能进行 Java 方法调用。使用 JNIEnv 提供的 JNI 方法,我们就可以获得属性和方法相对应的 jfieldID 和jmethodID:l GetFieldID :取得成员变量的 idl GetStaticFieldID :

19、取得静态成员变量的 idl GetMethodID :取得方法的 idl GetStaticMethodID :取得静态方法的 idcpp view plaincopyprint?1. jfieldID GetFieldID(jclass clazz, const char *name,const char *sig) 2. 3. jfieldID GetStaticFieldID(jclass clazz, const char*name, const char *sig) 4. 5. jmethodID GetStaticMethodID(jclass clazz, const char*

20、name, const char *sig) 6. 7. jmethodID GetMethodID(jclass clazz, const char *name,constchar *sig) 可以看到这四个方法的参数列表都是一模一样的,下面来分析下每个参数的含义:第一个参数 jclassclazz :上一节讲到的 jclass 类型,相当于 Java 中的 Class 类,代表一个 Java 类,而这里面的代表的就是我们操作的 Class 类,我们要从这个类里面取的属性和方法的 ID。第二个参数 constchar *name:这是一个常量字符数组,代表我们要取得的方法名或者变量名。第三个

21、参数 constchar *sig:这也是一个常量字符数组,代表我们要取得的方法或变量的签名。什么是方法或者变量的签名呢?我们来看下面的例子,如何来获得属性和方法 ID:cpp view plaincopyprint?1. public class NativeTest 2. 3. publicvoid show(int i) 4. 5. System.out.println(i); 6. 7. 8. 9. public void show(double d) 10. 11. System.out.println(d); 12. 13. 14. 15. 本地代码部分:cpp view plai

22、ncopyprint?1. /首先取得要调用的方法所在的类的 Class 对象,在 C/C+中即 jclass 对象 2. 3. jclass clazz_NativeTest=env-FindClass(“cn/itcast/NativeTest“); 4. 5. /取得 jmethodID 6. 7. jmethodID id_show=env-GetMethodID(clazz_NativeTest,“show”,“?“); 上述代码中的 id_show 取得的 jmethodID 到底是哪个 show 方法呢?由于 Java 语言有方法重载的面向对象特性,所以只通过函数名不能明确的让

23、JNI 找到 Java 里对应的方法。所以这就是第三个参数 sig 的作用,它用于指定要取得的属性或方法的类型签名。2) JNI 签名:l 基本类型以特定的大写字母表示l 引用类型类型签名 Java 类型 类型签名 Java 类型Z boolean B byte I intC char F floatS short B byteI int C charJ long S shortF float D doubleD double J longL fully-qualified-class(全限定的类) Z booleanJava 对象以 L 开头,然后以“/”分隔包的完整类型,例如 String

24、 的签名为:Ljava/lang/String;在 Java 里数组类型也是引用类型,数组以 开头,后面跟数组元素类型的签名,例如:int 签名就是 I ,对于二维数组,如 int 签名就是I, object 数组签名就是Ljava/lang/Object;l 方法签名(参数 1 类型签名参数 2 类型签名参数 3 类型签名.) 返回值类型签名注意:函数名,在签名中没有体现出来参数列表相挨着,中间没有逗号,没有空格返回值出现在()后面如果参数是引用类型,那么参数应该为: L 类型 ; 如果函数没有返回值,也要加上 V 类型例如:3) 根据获取的 ID,来取得和设置属性,以及调用方法。l 获得、

25、设置属性和静态属性取得了代表属性和静态属性的 jfieldID,就可以使用 JNIEnv 中提供的方法来获取和设置属性/静态属性。获取属性/静态属性的形式:GetField GetStaticField。设置属性/静态属性的形式:SetField SetStaticField。取得成员属性:cpp view plaincopyprint?1. jobject GetObjectField(jobjectobj, jfieldID fieldID); 2. 3. jboolean GetBooleanField(jobjectobj, jfieldID fieldID); 4. 5. jbyte

26、 GetByteField(jobjectobj, jfieldID fieldID); 取得静态属性:cpp view plaincopyprint?1. jobject GetStaticObjectField(jclassclazz, jfieldID fieldID); Java 方法 对应签名boolean isLedOn(void) ; ()Zvoid setLedOn(int ledNo); (I)String substr(String str, int idx, int count); (Ljava/lang/String;II)Ljava/lang/Stringchar f

27、un (int n, String s, int value); (ILjava/lang/String;I)Cboolean showMsg(View v, String msg); (Landroid/View;Ljava/lang/String;)Z2. jboolean GetStaticBooleanField(jclassclazz, jfieldID fieldID); 3. jbyte GetStaticByteField(jclassclazz, jfieldID fieldID); Get 方法的第一个参数代表要获取的属性所属对象或 jclass 对象,第二个参数即属性ID

28、。设置成员属性:cpp view plaincopyprint?1. void SetObjectField(jobjectobj, jfieldID fieldID, jobject val); 2. void SetBooleanField(jobjectobj, jfieldID fieldID,jboolean val); 3. void SetByteField(jobjectobj, jfieldID fieldID, jbyte val); 设置静态属性:cpp view plaincopyprint?1. void SetStaticObjectField(jobjectobj

29、, jfieldIDfieldID, jobject val); 2. void SetStaticBooleanField(jobjectobj, jfieldID fieldID,jboolean val); 3. void SetStaticByteField(jobjectobj, jfieldID fieldID, jbyte val); Set 方法的第一个参数代表要设置的属性所属的对象或 jclass 对象,第二个参数即属性ID,第三个参数代表要设置的值。l 调用方法取得了代表方法和静态方法的 jmethodID,就可以用在 JNIEnv 中提供的方法来调用方法和静态方法。调用静

30、态方法:cpp view plaincopyprint?1. CallStaticMethod(jclass clazz, jmethodID methodID,.); 2. 3. CallStaticMethodV(jclass clazz, jmethodID methodID,va_listargs); 4. 5. CallStatictMethodA(jclass clazz, jmethodID methodID,constjvalue *args); 上面的 Type 这个方法的返回值类型,如 Int,Char,Byte 等等。第一个参数代表调用的这个方法所属于的对象,或者这个静态

31、方法所属的类。第二个参数代表 jmethodID。后面的参数,就代表这个方法的参数列表了。上述方法的调用有三种形式:a) CallMethod(jobject obj, jmethodIDmethodID,.);cpp view plaincopyprint?1. / Java 方法 2. 3. public int show(int i,double d,char c) 4. 5. 6. 7. 8. 9. 10. 11./ 本地调用 Java 方法 12. 13.jint i=10L; 14. 15.jdouble d=2.4; 16. 17.jchar c=Ld; 18. 19.env-C

32、allIntMethod(obj, id_show, i, d, c); b) CallMethodV(jobject obj, jmethodIDmethodID,va_list args)这种方式使用较少。c) CallMethodA(jobject obj, jmethodIDmethodID,jvalue* v)这种调用方式其第三个参数是一个 jvalue 的指针。jvalue 类型是在 jni.h 头文件中定义的联合体 union,看它的定义:cpp view plaincopyprint?1. typedef union jvalue 2. jboolean z; 3. jbyte

33、 b; 4. jchar c; 5. jshort s; 6. jint i; 7. jlong j; 8. jfloat f; 9. jdouble d; 10. jobject l; 11. jvalue; 例如:cpp view plaincopyprint?1. jvalue * args=new jvalue3; 2. args0.i=12L; 3. args1.d=1.2; 4. args2.c=Lc; 5. jmethodIDid_goo=env-GetMethodID(env-GetObjectClass(obj),“goo“,“(IDC)V“); 6. env-CallVoi

34、dMethodA(obj,id_goo,args); 7. delete args; /释放内存 静态方法的调用方式和成员方法调用一样。3.5 本地创建 Java 对象1) 本地代码创建 Java 对象JNIEnv 提供了下面几个方法来创建一个 Java 对象:cpp view plaincopyprint?1. jobject NewObject(jclass clazz, jmethodID methodID,.); 2. jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args); 3. jobject NewObje

35、ctA(jclass clazz, jmethodID methodID,const jvalue *args) ; 本地创建 Java 对象的函数和前面本地调用 Java 方法很类似:第一个参数 jclass class 代表的你要创建哪个类的对象第二个参数 jmethodID methodID 代表你要使用哪个构造方法 ID 来创建这个对象。只要有 jclass 和 jmethodID ,我们就可以在本地方法创建这个 Java 类的对象。指的一提的是:由于 Java 的构造方法的特点,方法名与类名一样,并且没有返回值,所以对于获得构造方法的 ID 的方法 env-GetMethodID(c

36、lazz,method_name ,sig)中的第二个参数是固定为类名,第三个参数和要调用的构造方法有关,默认的 Java 构造方法没有返回值,没有参数。例如:cpp view plaincopyprint?1. jclassclazz=env-FindClass(“java/util/Date“); /取得java.util.Date 类的 jclass 对象 2. jmethodID id_date=env-GetMethodID(clazz,“Date“,“()V“); /取得某一个构造方法的jmethodID 3. jobject date=env-NewObject(clazz,id

37、_date); /调用NewObject 方法创建 java.util.Date 对象 2) 本地方法对 Java 字符串的操作在 Java 中,字符串 String 对象是 Unicoode(UTF-16)编码,每个字符不论是中文还是英文还是符号,一个字符总是占用两个字节。在 C/C+中一个字符是一个字节, C/C+中的宽字符是两个字节的。所以 Java 通过 JNI 接口可以将 Java 的字符串转换到C/C+的宽字符串(wchar_t*),或是传回一个 UTF-8 的字符串(char*)到 C/C+,反过来,C/C+可以通过一个宽字符串,或是一个 UTF-8 编码的字符串创建一个 Jav

38、a端的 String 对象。可以看下面的一个例子:在 Java 端有一个字符串 String str=“abcde“;,在本地方法中取得它并且输出:cpp view plaincopyprint?1. void native_string_operation (JNIEnv * env, jobject obj) 2. 3. /取得该字符串的 jfieldID 4. jfieldIDid_string=env-GetFieldID(env-GetObjectClass(obj), “str“, “Ljava/lang/String;“); 5. jstringstring=(jstring)(

39、env-GetObjectField(obj, id_string); /取得该字符串,强转为jstring 类型。 6. printf(“%s“,string); 7. 由上面的代码可知,从 java 端取得的 String 属性或者是方法返回值的 String 对象,对应在 JNI 中都是 jstring 类型,它并不是 C/C+中的字符串。所以,我们需要对取得的 jstring 类型的字符串进行一系列的转换,才能使用。JNIEnv 提供了一系列的方法来操作字符串:l const jchar *GetStringChars(jstring str, jboolean*isCopy) 将一个

40、 jstring 对象,转换为(UTF-16)编码的宽字符串(jchar*)。l const char *GetStringUTFChars(jstring str,jboolean *isCopy)将一个 jstring 对象,转换为(UTF-8)编码的字符串(char*)。这两个函数的参数中,第一个参数传入一个指向 Java 中 String 对象的 jstring 引用。第二个参数传入的是一个 jboolean 的指针,其值可以为NULL、JNI_TRUE、JNI_FLASE。如果为 JNI_TRUE 则表示开辟内存,然后把 Java 中的 String 拷贝到这个内存中,然后返回指向这

41、个内存地址的指针。如果为 JNI_FALSE,则直接返回指向 Java 中 String 的内存指针。这时不要改变这个内存中的内容,这将破坏 String 在 Java 中始终是常量的规则。如果是 NULL,则表示不关心是否拷贝字符串。使用这两个函数取得的字符,在不适用的时候,要分别对应的使用下面两个函数来释放内存。RealeaseStringChars(jstring jstr, const jchar*str)RealeaseStringUTFChars(jstring jstr, constchar* str)第一个参数指定一个 jstring 变量,即要释放的本地字符串的资源第二个参数

42、就是要释放的本地字符串3) 创建 Java String 对象cpp view plaincopyprint?1. jstring NewString(const jchar *unicode, jsizelen) / 根据传入的宽字符串创建一个 Java String 对象 2. jstring NewStringUTF(const char *utf) /根据传入的 UTF-8 字符串创建一个 Java String 对象 4) 返回 Java String 对象的字符串长度cpp view plaincopyprint?1. jsize GetStringLength(jstring j

43、str) /返回一个java String 对象的字符串长度 2. jsize GetStringUTFLength(jstring jstr) /返回一个java String 对象经过 UTF-8 编码后的字符串长度 3.6 Java 数组在本地代码中的处理我们可以使用 GetFieldID 获取一个 Java 数组变量的 ID,然后用 GetObjectFiled 取得该数组变量到本地方法,返回值为 jobject,然后我们可以强制转换为 jArray 类型。cpp view plaincopyprint?1. typedef jarray jbooleanArray; 2. typed

44、ef jarray jbyteArray; 3. typedef jarray jcharArray; 4. typedef jarray jshortArray; 5. typedef jarray jintArray; 6. typedef jarray jlongArray; 7. typedef jarray jfloatArray; 8. typedef jarray jdoubleArray; 9. typedef jarray jobjectArray; jArray 类型是 JNI 定义的一个对象类型,它并不是 C/C+的数组,如 int数组,double数组等等。所以我们要把

45、 jArray 类型转换为 C/C+中的数组来操作。JNIEnv 定义了一系列的方法来把一个 jArray 类型转换为 C/C+数组或把 C/C+数组转换为 jArray。cpp view plaincopyprint?1. jsize GetArrayLength(jarray array) / 获得数组的长度 2. jobjectArray NewObjectArray(jsize len, jclass clazz, jobjectinit) / 创建对象数组,指定其大小 3. jobject GetObjectArrayElement(jobjectArray array, jsize

46、index) / 获得数组的指定元素 4. void SetObjectArrayElement(jobjectArray array, jsizeindex,jobject val) / 设置数组元素 5. 6. jbooleanArrayNewBooleanArray(jsize len) / 创建Boolean 数组,指定其大小 7. jbyteArrayNewByteArray(jsize len) /下面的都类似,创建对应类型的数组,并指定大小 8. jcharArrayNewCharArray(jsize len) 9. jshortArrayNewShortArray(jsize len) 10. jintArrayNewIntArray(jsize len) 11. jlongArrayNewLongArray(jsize len) 12. jfloatArrayNewFloatArray(jsize len) 13. jdoubleArrayNewDoubleArray(jsize len) 14. 15./ 获得指定类型数组的元素 16.jboolean * GetBooleanArrayElements(jbooleanArray array,jboolean*isCopy) 17.jbyte * GetByteArrayEleme

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报