侧边栏壁纸
  • 累计撰写 57 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

12、本地方法

yilee
2023-04-04 / 0 评论 / 0 点赞 / 31 阅读 / 0 字
温馨提示:
本文最后更新于2024-05-31,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

十二、本地方法

12.1 从Java 程序中调用C 函数

  1. Java 编程语言使用关键字native 表示本地方法。关键字native 提醒编译器该方法将在外部定义。本地方法不包含任何Java 编程语言编写的代码,而且方法头后面直接跟着一个表示终结的分号。本地方法声明看上
    去和抽象方法声明类似。

    class HelloNative {
    	public static native void greeting();
    }
    
  2. 为了实现本地代码,需要编写一个相应的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 个十六进制数序列。

    如果你重栽了本地方法,也就是说, 你用相同的名宇提供了多个本地方法, 那么你必须在名称后附加两个下划线,后面再加上已编码的参数类型。

  3. 运行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);
    
  4. 将本地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 的目录。

  5. 处理本地代码流程

    0d873456a51f494e96953302470c87b1

  6. 将一个本地方法链接到Java 程序中的步骤:

    • 在Java 类中声明一个本地方法。
    • 运行javah 以获得包含该方法的C 声明的头文件。
    • 用C 实现该本地方法。
    • 将代码置千共享类库中。
    • 在Java 程序中加载该类库。

12.2 数值参数与返回值

  1. Java 本地接口定义了jint 、jlong 等类型。用于替代C语言中的平台兼容性。在头文件jni. h 中,这些类型被typedef 语句声明为在目标平台上等价的类型。该头文件还定义了常量JNI_FALSE = 0 和JNI_TRUE = 1 。Java 数据类型和C 数据类型的对应关系如下:

    b284b8746570458d95bc79f6bf17512b

12.3 字符串参数

  1. Java 编程语言中的字符串是UTF-16 编码点的序列,而C 的字符串则是以null 结尾的字节序列。Java 本地接口有两组操作字符串的函数,一组把Java 字符串转换成“改良的UTF-8" 字节序列,另一组将它们转换成UTF-16 数值的数组,也就是说转换成jchar 数组。

  2. 带有字符串参数的本地方法实际上都要接受一个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;
    }
    
  3. 所有对JNI 函数的调用都使用到了env 指针,该指针是每一个本地方法的第一个参数。env 指针是指向函数指针表的指针。所以,你必须在每个JNI 调用前面加上 (*env)->,以便解析对函数指针的引用。而且, env 是每个JNI 函数的第一个参数。

    c2dfda0ce7594e49899eac14e0fa47fe

    C++ 中对JNI 函数的访问要简单一些。JNIEnv 类的C++版本有一个内联成员函数,它负责帮你查找函数指针。

    // 调用NewStringUTF 函数
    jstr = env->NewStringUTF(greeting);
    
  4. NewStringUTF 函数可以用来构造一个新的jstring;用GetStringUTFChars 函数读取现有j string 对象的内容,函数返回指向描述字符串的”改良UTF-8 " 字符的const jbyte*指针。

  5. 虚拟机必须知道你何时使用完字符串,这样它就能进行垃圾回收。基于这个原因,你必须调用ReleaseStringUTFChars 函数。调用GetStringRegion 或GetStringUTFRegion 方法来提供你自己的缓存,以存放字符串的字符。调用GetStringUTFLength 函数返回字符串的”改良UTF-8 " 编码所需的字符个数。

  6. 从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 访间域

  1. 访问实例域

    • 实现类 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必须先获得一个表示类的值,有两种方法可以实现此目的。

      1. GetObjectClass 函数可以返回任意对象的类。

        jclass class_Employee =(*env) GetObjectClass(env, this_obj);
        
      2. 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);
      
  2. 访问静态域

    • 访问静态域和访问非静态域类似。你要使用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 编码签名

  1. 将数据类型的名称和方法签名进行`混编"的规则,编码方案如下:

    签名 类型
    B byte
    C char
    D double
    F float
    I lnt
    J long
    Lclassname; 类的类型
    s short
    V vold
    z boolean
    [类型编码 数组

    数组例子:

    // 字符串数数组
    [Ljava/lang/String;
    // float[][]
    [[F
    
  2. 一个方法的完整签名,需要把括号内的参数类型都列出来,然后列出返回值类型。

    // 一个接收两个整型参数并返回一个整数的方法编码
    (II)I
    // 构造器 Employee(java.lang.String, double, java.util.Date) 签名
    (Ljava/lang/String; Ljava/util/Date;)V
    

12.6 调用Java 方法

  1. 实例方法(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);
      	..
      }
      
  2. 静态方法

    • 本地方法调用静态方法与调用非静态方法类似

      • 要用 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;
      
  3. 构造器

    • 调用NewObject 函数来调用构造器。可以通过指定方法名为 <init> ,并指定构造器(返回值为void ) 的编码签名, 从GetMethod ID 函数中获取该调用必需的方法1D 。

      jobject obj_new =(*env)->NewObject(env, class, methodID, construction parameters);
      
  4. 另一种方法调用

    • 有若干种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 访问数组元素

  1. Java 编程语言的所有数组类型都有相对应的C 语言类型。

    a5f074bde3a64b46bc6254c41859b574

    在C 中,所有这些数组类型实际上都是jobject 的同义类型。C++中它们被安排在下图所示的继承层次结构中。jarray 类型表示一个通用数组。

    c1344963ff7547fd86f2dd393a862a6a

  2. 数组关操作函数

    • 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);
      
  3. 直接缓存操作,java.nio 包中使用了直接缓存来支持更高效的输入轮出操作,并尽可能减少本地和Java
    数组之间的复制操作。

    jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity)
    void* GetDirectBufferAddress(JNIEnv* env, jobject buf)
    jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf)
    
  4. 操作 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 错误处理

  1. 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");
    
  2. Throw 和 ThrowNew 都仅仅只是发布异常,它们不会中断本地方法的控制流。只有当该方法返回时, Java 虚拟机才会抛出异常。每一个对Throw 和ThrowNew 的调用语句之后总是紧跟着return 语句。

  3. 异常相关操作函数

    • ExceptionOccurred 确认是否有异常抛出。没有任何异常等待处理,方法返回NULL,否则返回一个当前异常对象的引用。

      jthrowable obj_exc = (*env)->ExceptionOccurred(env);
      
    • ExceptionCheck 只要检查是否有异常抛出,而不需要获得异常对象的引用

      jboolean occurred = (*env)->ExceptionCheck(env);
      
    • ExceptionClear 本地方法也可以分析异常对象,确定它是否能够处理该异常。如果能够处理,则使用该方法关闭该异常。

      (*env) ->ExceptionClear(env);
      
  4. 处理 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

  1. 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";
    
  2. 一旦设置完虚拟机,就可以如前面小节介绍的那样调用Java 方法了。只要按常规方法使用env 指针即可。只有在调用invocation API 中的其他函数时,才需要jvm 指针。最终要的函书如下:

    (*jvm)->DestroyJavaVM(jvm);
    
  3. 调用 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 注册表

  1. Windows 注册表概述

    • Windows 注册表是一个存放Windows 操作系统和应用程序的配置信息的数据仓库。它提供了对系统和应用程序参数的单点管理和备份。
  2. 类型质询函数

    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)
// 装载指定名字的库,该库位千库搜索路径中。定位该库的确切方法依赖于操作系统。
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区