十二、本地方法
12.1 从Java 程序中调用C 函数
-
Java 编程语言使用关键字native 表示本地方法。关键字native 提醒编译器该方法将在外部定义。本地方法不包含任何Java 编程语言编写的代码,而且方法头后面直接跟着一个表示终结的分号。本地方法声明看上
去和抽象方法声明类似。class HelloNative { public static native void greeting(); }
-
为了实现本地代码,需要编写一个相应的C 函数,你必须完全按照Java 虚拟机预期的那样来命名这个函数。
- 使用完整的Java 方法名,比如: HelloNative.greeting 。如果该类属于某个包,那么在前面添加包名,比如: com.horstmann.HelloNative.greeting 。
- 用下划线替换掉所有的句号,并加上Java_ 前缀,例如, Java_HelloNative_greeting 或Java_com_horstmann_HelloNative_greeting 。
- 如果类名含有非ASCII 字母或数字,如:‘_’ ,‘$’ 或是大千’\u007F’ 的Unicode 字符,用 _0xxxx 来替代它们, xxxx 是该字符的Unicode 值的4 个十六进制数序列。
如果你重栽了本地方法,也就是说, 你用相同的名宇提供了多个本地方法, 那么你必须在名称后附加两个下划线,后面再加上已编码的参数类型。
-
运行javah 实用程序, 它能够自动生成函数名。
# 调用javah javah -classpath C:\Users\Galaxy\Desktop top.liheji.HelloNative
// 生成的 top_liheji_HelloNative.h 文件内容 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class top_liheji_HelloNative */ #ifndef _Included_top_liheji_HelloNative #define _Included_top_liheji_HelloNative #ifdef __cplusplus extern "C" { #endif /* * Class: top_liheji_HelloNative * Method: greeting * Signature: ()V */ JNIEXPORT void JNICALL Java_top_liheji_HelloNative_greeting (JNIEnv *, jclass); // jclass 即代表类的引用 #ifdef __cplusplus } #endif #endif
你可以使用C++实现本地方法。必须将实现本地方法的函数声明为extern"C"
extern "C" JNIEXPORT void JNICALL Java_top_liheji_HelloNative_greeting (JNIEnv *, jclass);
-
将本地C 代码编译到一个动态装载库中
// Linux 下的GnuC 编译器 gcc -fPTC -I jdk/inelude -I jdk/inelude/linux -shared -o libHelloNative.so HelloNative.c // Solaris 操作系统的Sun 编译器 cc -G -I jdk/inelude -I jdk/inelude/solaris -o libHelloNative.so HelloNative.c // Windows 下的微软编译器 cl -I jdk\inelude -I jdk\inelude\win32 -LD HelloNative.c -FeHelloNative.dll // Cygwin 编程环境 gcc -mno-cygwin -D _int64="long long" -I jdk/inelude/ -I jdk/inelude/win32 -shared -Wl,--add-stdcall-alias -o HelloNative.dll HelloNative.c
jdk 是含有JDK 的目录。
-
处理本地代码流程
-
将一个本地方法链接到Java 程序中的步骤:
- 在Java 类中声明一个本地方法。
- 运行javah 以获得包含该方法的C 声明的头文件。
- 用C 实现该本地方法。
- 将代码置千共享类库中。
- 在Java 程序中加载该类库。
12.2 数值参数与返回值
-
Java 本地接口定义了jint 、jlong 等类型。用于替代C语言中的平台兼容性。在头文件jni. h 中,这些类型被typedef 语句声明为在目标平台上等价的类型。该头文件还定义了常量JNI_FALSE = 0 和JNI_TRUE = 1 。Java 数据类型和C 数据类型的对应关系如下:
12.3 字符串参数
-
Java 编程语言中的字符串是UTF-16 编码点的序列,而C 的字符串则是以null 结尾的字节序列。Java 本地接口有两组操作字符串的函数,一组把Java 字符串转换成“改良的UTF-8" 字节序列,另一组将它们转换成UTF-16 数值的数组,也就是说转换成jchar 数组。
-
带有字符串参数的本地方法实际上都要接受一个jstring 类型的值,而带有字符串参数返回值的本地方法必须返回一个jstring 类型的值。JNI 函数将读入并构造出这些 jstring 对象。
// 对NewStringUTF 函数的一个调用 JNIEXPORT void JNICALL Java_top_liheji_HelloNative_greeting(JNIEnv *, jclass) { jstring jstr; char greeting[] = "Hello, Native World\n"; jstr = (*env)->NewStringUTF(env, greeting); return jstr; }
-
所有对JNI 函数的调用都使用到了env 指针,该指针是每一个本地方法的第一个参数。env 指针是指向函数指针表的指针。所以,你必须在每个JNI 调用前面加上 (*env)->,以便解析对函数指针的引用。而且, env 是每个JNI 函数的第一个参数。
C++ 中对JNI 函数的访问要简单一些。JNIEnv 类的C++版本有一个内联成员函数,它负责帮你查找函数指针。
// 调用NewStringUTF 函数 jstr = env->NewStringUTF(greeting);
-
NewStringUTF 函数可以用来构造一个新的jstring;用GetStringUTFChars 函数读取现有j string 对象的内容,函数返回指向描述字符串的”改良UTF-8 " 字符的const jbyte*指针。
-
虚拟机必须知道你何时使用完字符串,这样它就能进行垃圾回收。基于这个原因,你必须调用ReleaseStringUTFChars 函数。调用GetStringRegion 或GetStringUTFRegion 方法来提供你自己的缓存,以存放字符串的字符。调用GetStringUTFLength 函数返回字符串的”改良UTF-8 " 编码所需的字符个数。
-
从C代码访问 Java字符串
jstring NewStringUTF(JNIEnv* env, const char bytes[]) // 根据以全0 字节结尾的”改良UTF-8" 字节序列,返回一个新的Java 字符串对象,或者当字符串无法构建时,返回NULL 。 jsize GetStringUTFLength(JNIEnv* env , jstring string) // 返回进行UTF-8 编码所需的字节个数。(作为终止符的全0 字节不计入内) const jbyte* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) // 返回指向字符串的”改良UTF-8 " 编码的指针,或者当不能构建字符数组时返回NULL 。直到ReleaseStringUTFChars 函数调用前,该指针一直有效。isCopy 指向一个jboolean, 如果进行了复制,则填入JNI_TRUE, 否则填入JNI_FALSE 。 void ReleaseStringUTFChars(JNIEnv* env, jstring string, const jbyte bytes[]) // 通知虚拟机本地代码不再需要通过bytes(GetStri ngUTFChars 返回的指针)访问Java 字符串。 void GetStringRegion(JNIEnv *env, jstring string, jsize start, jsize length, jchar *buffer) // 将一个UTF-16 双字节序列从字符串复制到用户提供的尺寸至少大于2 x length 的缓存中。 void GetStringUTFRegion(JNIEnv *env, jstring string, jsize start, jsize length, jbyte *buffer) // 将一个“改良UTF-8 " 字符序列从字符串复制到用户提供的缓存中。为了存放要复制的字节,该缓存必须足够长。最坏情况下,要复制3 x 1 ength 个字节。 jstring NewString(JNIEnv* env, const jchar chars[], jsize length) //根据Unicode 字符串返回一个新的Java 字符串对象,或者在不能构建时返回NULL 。 // 参数: env JNI 接口指针 // chars 以null 结尾的UTF-16 字符串 // length 字符串中字符的个数 // jsize GetStringLength(JNIEnV* env, jstring string) // 返回字符串中字符的个数。 const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* isCopy) // 返回指向字符串的Unicode 编码的指针,或者当不能构建字符数组时返回NULL 。直到ReleaseStringChars 函数调用前,该指针一直有效。isCopy 要么为NULL; 要么在进行了复制时,指向用JNI_TRUE 填充的jboolean , 否则指向用JNI_FALSE 填充的j boolean 。 void ReleaseStringChars(JNIEnv* env, jstring string, const jchar chars[]) // 通知虚拟机本地代码不再需要通过chars ( GetStri ngChars 返回的指针)访问Java字符串。
12.4 访间域
-
访问实例域
-
实现类 Employee 类中的 raiseSalary方法
public void raiseSalary(double byPercent) { salary *= 1 + byPercent / 100 }
-
运行 javah 得到函数原型
JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdouble); // jobject,对隐式的this 参数对象的引用。
第二个参数不再是jclass 类型而是jobject 类型。实际上,它和this 引用等价。静态方法得到的是类的引用,而非静态方法得到的是对隐式的this 参数对象的引用。
-
JNI 要求程序员通过调用特殊的JNI 函数来获取和设置数据的值。即使用GetdoubleField /SetDoubleField 、GetlntField/SetlntField 、GetObjectField/SetObjectField等函数。
//通用语法 x = (*env)->GetXxxField(env, this_obj, fieldID); (*env)->SetXxxField(env, this_obj, fieldID, x); // fieldID 是一个特殊类型jfieldID 的值,jfieldIO 标识结构中的一个域
fieldID 是一个特殊类型jfieldID 的值,jfieldIO 标识结构中的一个域。获取fieldID必须先获得一个表示类的值,有两种方法可以实现此目的。
-
GetObjectClass 函数可以返回任意对象的类。
jclass class_Employee =(*env) GetObjectClass(env, this_obj);
-
FindClass 函数可以让你以字符串形式来指定类名(以/代替句号作为包名之间的分隔符)
jclass class_String = (*env)->FindClass(env, "java/lang/String");
使用GetFieldID来获取fieldID,必须提供域的名字、它的签名以及它的类型的编码。
jfieldID id_salary = (*env)->CetFieldID(env, class_Employee, "salary", "D");
-
-
完整代码
JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdouble){ // get class jclass class_Employee =(*env) GetObjectClass(env, this_obj); // get field ID jfieldID id_salary = (*env)->CetFieldID(env, class_Employee, "salary", "D"); // get field value jdouble salary= (*env)->GetDoubleField(env, this_obj, id_salary); salary *= 1 + byPercent / 100; (*env)->SetDoubleField(env, this_obj, id_salary, salary) ; }
类引用只在本地方法返回之前有效。因此,不能在你的代码中缓存GetobjectClass的返回值。必须在每次执行本地方法时都调用GetObjectClass 。
调用NewGlobalRef 来锁定引用后可以后续调用,结束对类的使用时,务必调用DeleteGlobalRef 释放锁定。
static jclass class_X = 0; static jfieldID id_a; ... if (class_X == 0) { jclass cx= (*env)->GetObjectClass(env, obj); class_X = (*env)->NewGlobalRef(env, cx); id_a = (*env)->GetFieldID(env, els, "a", "..."); } // 释放锁定 (*env)->DeleteGlobalRef(env, class_X);
-
-
访问静态域
-
访问静态域和访问非静态域类似。你要使用GetStaticFieldID 和GetStaticXxxField/SetStaticXxxField 函数。它们几乎与非静态的情形一样,两个区别如下:
- 由千没有对象,必须使用FindClass 代替GetObjectClass 来获得类引用。
- 访问域时,要提供类而非实例对象。
-
得到System.out 的引用
// get class jclass class_System = (*env) FindClass(env, "java/lang/System"); // get field ID jfieldID id_out = (*env)->CetStaticFieldID(env, class_System, "out", "Ljava/io/PrintStream;"); // get field value jdouble obj_out = (*env)->GetStaticObjectField(env, class_System, id_out);
-
访问实例域
jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldSignature[]) // 返回类中一个域的标识符。 Xxx GetXxxField(JNIEnv *env, jobject obj, jfieldID id) // 返回域的值。域类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。 void SetxxxField(JNIEnv *env, jobject obj, jfieldID id, Xxx value) // 把某个域设置为一个新值。域类型Xxx 是Object 、Boolean 、Byte 、Char 、S hort 、Int 、Long 、Float 或Double 之一。 ejfieldID GetStaticFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldSignature[]) // 返回某类型的一个静态域的标识符。 Xxx GetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id) // 返回某静态域的值。域类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。 void SetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id, Xxx value) // 把某个静态域设置为一个新值。域类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。
-
12.5 编码签名
-
将数据类型的名称和方法签名进行`混编"的规则,编码方案如下:
签名 类型 B byte C char D double F float I lnt J long Lclassname; 类的类型 s short V vold z boolean [类型编码 数组 数组例子:
// 字符串数数组 [Ljava/lang/String; // float[][] [[F
-
一个方法的完整签名,需要把括号内的参数类型都列出来,然后列出返回值类型。
// 一个接收两个整型参数并返回一个整数的方法编码 (II)I // 构造器 Employee(java.lang.String, double, java.util.Date) 签名 (Ljava/lang/String; Ljava/util/Date;)V
12.6 调用Java 方法
-
实例方法(P771)
-
从C 中调用任何Java 方法,根据方法的返回类型,用Void 、Int 、Object 等来替换 Xxx 。调用JNI 函数GetMethodID , 并且提供该类、方法的名字和方法签名来获得方法ID 。
(*env)->CallXxxMethod (env, implicit parameter, methodID, explicit parameters)
-
增强Printf 类,实现与C函数f printf 类似的方法。(P771)
class Printf { public native static void fprint(PrintWriter out, String s, double x); .. }
-
-
静态方法
-
本地方法调用静态方法与调用非静态方法类似
- 要用 GetStaticMethodID 和 CallStaticXxxMethod 函数。
- 当调用方法时,要提供类对象,而不是隐式的参数对象。
-
调用以下静态方法
System.getProperty("java.class.path")
// 用 FindClass 找到类 jclass class_System = (*env)->FindClass(env, "java/lang/System"); // 获取方法静态 getProperty 方法的ID jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, "getProperty","(Ljava/lang/String;)Ljava/lang/String;"); // 使用CallStaticObjectMethod 调用函数 jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "java.class.path")); // 转型为jstring jstring str_ret = (jstring) obj_ret;
-
-
构造器
-
调用NewObject 函数来调用构造器。可以通过指定方法名为
<init>
,并指定构造器(返回值为void ) 的编码签名, 从GetMethod ID 函数中获取该调用必需的方法1D 。jobject obj_new =(*env)->NewObject(env, class, methodID, construction parameters);
-
-
另一种方法调用
-
有若干种JNI 函数的变体都可以从本地代码调用Java 方法。CallNonvirtualXxxMethod 函数接收一个隐式参数、一个方法ID 、一个类对象(必须对应千隐式参数的超类)和一个显式参数。所有调用函数都有后缀"A” 和“V” 的版本,用于接收数组中或va_l i st 中的显式参数
-
执行Java方法
jmethodID GetMethodID(JNIEnv *env, jcl ass cl , con st char name[], const char methodSignature[]) // 返回类中某个方法的标识符。 Xxx CallXxxMethod(JNIEnv *env, jobject obj, jmethodID id, args) Xxx CallXxxMethodA(JNIEnv *env, jobject obj, jmethodID id, jvalue args[]) Xxx CallXxxMethodV(JNIEnv *env, jobject obj, jmethodID id, va_list args) // 调用一个方法。返回类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID 之后即可。 // 第二个函数接受jv alue 数组中的方法参数,其中jvalue 是一个联合体,定义如下: // typedef union jvalue { // jboolean z; // jbyte b; // jchar c; // jshort s; // jint i; // jlong j; // jfloat f; // jdouble d; // jobject l; // } jvalue; // 第三个函数接收C 头文件stdarg.h 中定义的va_list 中的方法参数。 Xxx CallNonvirtualXxxMethod(JNIEnv *env, jobject obj, jclass cl, jmethodID id, args) Xxx CallNonvirtualXxxMethodA(JNIEnv *env, jobject obj, jclass cl, jmethodID id, jvalue args[]) Xxx CallNonvirtualXxxMethodV(JNIEnv *env, jobject obj, jclass cl, jmethodID id, va_list args) // 调用一个方法,并绕过动态调度。返回类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID 之后即可。第二个函数接受jvalue 数组中的方法参数。第三个函数接受C 头文件stdarg.h 中定义的va_list 中的方法参数。 jmethodID GetStaticMethodID(JNIEnv *env, jclass cl, const char name[], const charmethodSignature[]) // 返回类的某个静态方法的标识符。 Xxx CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, args) Xxx CallStaticXxxMethodA(JNIEnv *env, jclass cl, jmethodID id, jvalue args[]) Xxx CallStaticXxxMethodV(JNIEnv *env, jclass cl, jmethodID id, va_list args) // 调用一个静态方法。返回类型Xxx 是Object 、Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID 之后即可。第二个函数接受jvalue 数组中的方法参数。第三个函数接受C 头文件stdarg.h 中定义的va_list 中的方法参数。 jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, args) jobject NewObjectA(JNIEnv *env, jclass cl, jmethodID id, jvalue args[]) jobject NewObjectV(JNIEnv *env, jclass cl, jmethodID id, va_list args) // 调用构造器。函数ID 从带有函数名为“ <init>" 和返回类型为void 的G etMethodID获取。第一个函数有可变数量参数,只要把方法参数附加到方法ID 之后即可。第二个函数接收jvalue 数组中的方法参数。第三个函数接收C 头文件stdarg.h 中定义的 va_list 中的方法参数。
-
12.7 访问数组元素
-
Java 编程语言的所有数组类型都有相对应的C 语言类型。
在C 中,所有这些数组类型实际上都是jobject 的同义类型。C++中它们被安排在下图所示的继承层次结构中。jarray 类型表示一个通用数组。
-
数组关操作函数
-
GetArrayLength 返回数组的长度。
jarray array = ...; jsize length = (*env)->GetArrayLength(env, array);
-
ObjectArrayElement / Set ObjectArrayElement 访问对象数组的元素。
jobjectArray array = ...; int i, j; jobject x = (*env) ->GetObjectArrayElement(env, array, i); (*env)->SetObjectArrayElement(env, array, j, x);
-
GetXxxArrayElements 返回一个指向数组起始元素的C 指针。
- 与普通的字符串一样,当你不再需要该指针时,必须记得要调用 ReleaseXxxArrayElements 函数通知虚拟机。此处Xxx必须是基本类型,不能是Object 。
- 另一方面,由千指针可能会指向一个副本,只有调用相应的 ReleaseXxxArrayElements 函数时,你所做的改变才能保证在源数组里得到反映。
-
GetXxxArrayRegion / SetXxxArrayRegion 把一定范围内的元素从Java 数组复制到C 数组中或从C 数组复制到 Java 数组中。
-
NewXxxArray 在本地方法中创建新的Java 数组。需要指定长度、数组元素的类型和所有元素的初始值(典型的是NULL) 。
jclass class_Employee = (*env)->FindClass(env, "Employee"); jobjectArray array_e = (*env)->NewObjectArray(env, 100, class_Employee, NULL);
-
基本类型的数组要简单一些。只需提供数组长度。数组被0 填充。
jdoubleArray array_d = (*env)->NewDoubleArray(env, 100);
-
-
直接缓存操作,java.nio 包中使用了直接缓存来支持更高效的输入轮出操作,并尽可能减少本地和Java
数组之间的复制操作。jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) void* GetDirectBufferAddress(JNIEnv* env, jobject buf) jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf)
-
操作 Java数组
jsize GetArraylength(JNIEnv *env, jarray array) // 返回数组中的元素个数。 jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) // 返回数组元素的值。 void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value) // 将数组元素设为新值。 Xxx* GetXxxArrayElements (JNIEnv *env, j array array, jboo lean* ; sCopy) // 产生一个指向Java 数组元素的C 指针。域类型Xxx 是Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。指针不再使用时,该指针必须传递给ReleaseXxxArrayElements 。iscopy 可能是NULL, 或者在进行了复制时,指向用JNLTRUE 填充的jboolean; 否则,指向用JNLFALSE 填充的j boolean 。 void ReleaseXxxArrayElements(JNIEnv *env, jarray array , Xxx elems[], jint mode) // 通知虚拟机通过GetXxxArrayElements 获得的一个指针已经不再需要了。Mode 是0(更新数组元素后释放elems 缓存)、JNI_COMMIT (更新数组元素后不释放el ems 缓存)或JNI_ABORT (不更新数组元素便释放elems 缓存)之一。 void GetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[]) // 将Java 数组的元素复制到C 数组中。域类型Xxx 是Boolean 、Byte 、Char 、Short 、Int 、Long 、Float 或Double 之一。 void SetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[]) // 将C 数组的元素复制到Java 数组中。域类型Xxx 是Boolean 、Byte 、Char 、Short 、Int 、L ong 、Float 或Double 之一。
12.8 错误处理
-
C 语言没有异常,必须调用Throw 或ThrowNew 函数来创建一个新的异常对象。当本地方法退出时, Java 虚拟机就会抛出该异常。
// 使用Throw 函数,需要调用NewObject 来创建一个T hrowable 子类的对象。 jclass cl ass_EOFException = (*env)->FindClass(env, "java/io/EOFException"); jmethodID id_EOFException = (*env)->GetMethodID(env, class_EOFException, "<init>", "()V"); /* ID of no-argument constructor */ jthrowable obj_exc = (*env)->NewObject(env, class_EOFException, id_EOFException); (*env)->Throw(env, obj_exc); // 调用ThrowNew 会更加方便,因为只需提供一个类和一个“改良UTF-8" 字节序列,该函数就会构建一个异常对象。 (*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/EOFException"), "Unexpected end of file");
-
Throw 和 ThrowNew 都仅仅只是发布异常,它们不会中断本地方法的控制流。只有当该方法返回时, Java 虚拟机才会抛出异常。每一个对Throw 和ThrowNew 的调用语句之后总是紧跟着return 语句。
-
异常相关操作函数
-
ExceptionOccurred 确认是否有异常抛出。没有任何异常等待处理,方法返回NULL,否则返回一个当前异常对象的引用。
jthrowable obj_exc = (*env)->ExceptionOccurred(env);
-
ExceptionCheck 只要检查是否有异常抛出,而不需要获得异常对象的引用
jboolean occurred = (*env)->ExceptionCheck(env);
-
ExceptionClear 本地方法也可以分析异常对象,确定它是否能够处理该异常。如果能够处理,则使用该方法关闭该异常。
(*env) ->ExceptionClear(env);
-
-
处理 Java异常
jint Throw(JNIEnv *env, jthrowable obj) // 准备一个在本地代码退出时抛出的异常。成功时返回0, 失败时返回一个负值。 jint ThrowNew(JNIEnv *env, jclass cl, canst char msg[]) // 准备一个在本地代码退出时抛出的类型为cl 的异常。成功时返回0, 失败时返回一个负值。msg 是表示异常对象的String 构造参数的”改良UTF-8" 字节序列 jthrowable ExceptionOccurred(JNIEnv *env) // 如果有异常挂起, 则返回该异常对象,否则返回NULL 。 jboolean ExceptionCheck(JNIEnv *env) // 如果有异常挂起,则返回true 。 void ExceptionClear(JNIEnv *env) // 清除挂起的异常。
12.9 使用调用API
-
C 或者 C++的程序调用Java代码,对JNI_CreateJavaVM 的调用将创建虚拟机,并且使指针jvm 指向虚拟机,使指针 env 指向执行环境。可以给虚拟机提供任意数目的选项,这只需增加选项数组的大小和vm_args.nOptions 的值。
// 初始化虚拟机 JavaVMOption options[1]; JavaVMinitArgs vm_args; JavaVM *jvm; JNIEnv *env; options[0].optionString = "-Djava.class.path=."; memset(&vm_args, 0, sizeof(vm_args)); vm_args.version = JNI_VERSION_l_2; vm_args.nOptions = 1; vm_args.options = options; JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args); // 钝化即时编译器。 options[i].optionString = "-Djava.compiler=NONE";
-
一旦设置完虚拟机,就可以如前面小节介绍的那样调用Java 方法了。只要按常规方法使用env 指针即可。只有在调用invocation API 中的其他函数时,才需要jvm 指针。最终要的函书如下:
(*jvm)->DestroyJavaVM(jvm);
-
调用 API函数
jint JNI_CreateJavaVM(JavaVM** p_jvm, void** p_env, JavaVMinitArgs* vm_args) // 初始化Java 虚拟机。如果成功,则返回0, 否则返回JNI_ERR 。 // 参数: p_jvm 填入指向调用API 函数表的指针 // p_env 填入指向JNI 函数表的指针 // vm_args 虚拟机参数 jint DestroyJavaVM(JavaVM* jvm) // 销毁虚拟机。如果成功,则返回0, 否则返回一个负值。该函数必须通过一个虚拟机指针调用。例如,(* jvm)->DestroyJavaVM(jvm) 。
12.10 完整的示例:访间Windows 注册表
-
Windows 注册表概述
- Windows 注册表是一个存放Windows 操作系统和应用程序的配置信息的数据仓库。它提供了对系统和应用程序参数的单点管理和备份。
-
类型质询函数
jboolean IsAssignableFrom(JNIEnv *env, jclass cll , jclass cl2) // 如果第一个类的对象可以赋给第二个类的对象, 则返回JNI_TRUE , 否则返回JNI_FALSE 。这个函数可以测试: 两个类是否相同, ell 是否是cl2 的子类, cl2 是否表示一个由ell 或它的一个超类实现的接口。 jclass GetSuperclass(JNIEnv *env, jclass cl) // 返回某个类的超类。如果cl 表示Object 类或一个接口, 则返回NULL 。
API注释
12.1 从Java 程序中调用C 函数
java.lang. System 1.0
void loadlibrary(String libname)
// 装载指定名字的库,该库位千库搜索路径中。定位该库的确切方法依赖于操作系统。
评论区