本地方法(JNI)——访问域+字符串参数

【0】README

1) 本文部分文字描述 转自 core java volume 2 , 旨在理解 本地方法(JNI)——访问域+字符串参数 的基础知识 ;
2) for source code, please visit https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter12/chapter12_4


【1】本地方法(JNI)——访问域相关

0)相关JNI函数定义:

  • 0.1)GetDoubleField 和 SetDoubleField函数: 获取和设置fieldID 的值, 因为salary是 double 类型的;
/* get the field value */ 
 jdouble salary = (*env)->GetDoubleField(env, obj, id_salary);   
 salary += increment;   
 /* set the field value */
 (*env)->SetDoubleField(env, obj, id_salary, salary);
  • 0.2)对于其他类型可以使用的函数有: GetIntField/SetIntField, GetObjectField/SetObjectField;其通用语法是: (干货——这叫调用特殊的JNI函数来获取和设置数据的值)
x=(*env)->GetXxxField(env, this_obj, fieldId);
(*env)->SetXxxField(env, this_obj, fieldID, x);
  • 0.3)这里 fieldID: 他是一个特殊类型 jfieldID 的值, jfieldID 标识结构中的一个域, 而 Xxx 代表java 数据类型(Object, Boolean, Byte 或其他);
  • 0.4)为了获得 fieldID, 必须先获得一个表示类的值, 有两种方法可以实现此目的。

    • 0.4.1)GetObjectClass函数:可以返回任意对象的类; 如,

      jclass class_Employee = (*env)->GetObjectClass(env, obj);

    • 0.4.2)FindClass函数: 可以让你以字符串形式来指定类名(要用 / 代替句号作为包名间的分隔符):

      jclass class_string = (*env)->FindClass(env, “java/lang/String”);

  • 0.5)之后可以使用GetFieldId 函数: 获得fieldId, 必须提供域的名字, 他的签名 和 他的类型编码;

    jfield id_salary = (*env)->GetFieldId(env, class_Employee, “salary”, “D”); // 字符串D 表示 double;

1)our destination(调用 Employee类的本地方法)
这里写图片描述

2) detailed steps:

  • step1) javah 导出类的头文件;
    这里写图片描述

  • step2)拷贝上述头文件中 raiseSalary的函数原型 到新建的 Employee.c中, 并提供 raiseSalary 的 C 语言实现;

    
    #include "com_corejava_chapter12_4_Employee.h"
    
    
    #include <stdio.h>
    
    JNIEXPORT void JNICALL Java_com_corejava_chapter12_14_Employee_raiseSalary(
                                JNIEnv * env, jobject obj, jdouble increment)
    {
        /* get the class == Employee 类 */
        jclass class_Employee = (*env)->GetObjectClass(env, obj);
    
        /* get the field ID == salary域标识符 */
        jfieldID id_salary = (*env)->GetFieldId(env, class_Employee, "salary", "D");
        // attention jfieldID not ifieldId ,  GetFieldId not GetFieldId
    
        /* get the field value == salary 域值*/
        jdouble salary = (*env)->GetDoubleField(env, obj, id_salary);
    
        salary += increment;
    
        /* set the field value == salary 域值设置器 */
        (*env)->SetDoubleField(env, obj, id_salary, salary);
    }
  • 这里写图片描述

  • Warning)

    • w1)类引用只在本地方法返回之前有效;因此,不能在你的代码中缓存GetObjectClass 的返回值;
    • w2)不要将类引用保存下来以供以后的方法调用重复使用;
    • w3)必须在每次执行本地方法时都调用 GetObjectClass, 如果你无法忍受这一点, 必须调用 NewGlobalRef 来锁定该引用;

      static jclass class_X=0;
      static jfieldId id_a;

      if(class_X == 0)
      {
      jclass cx = (*env)->GetObjectClass(env, obj); // env == 函数列表指针;
      class_X = (*env)->NewGlobalRef(env, cx); // 锁定引用;
      id_a = (*env)->GetFieldId(env, cls, “a”, “……”);
      }

    • w4) 现在,你可以在后买你的调用中使用类引用和域ID了。 当你结束对类的使用时, 务必调用:

      (*env)->DeleteGlobalRef(env, class_X);

  • step3) 编译 Employee.c 文件,并编译到 动态装载库 libEmployee.so;
    这里写图片描述

  • step4)将动态装载库拷贝到 java.lib.path 路径下, 并运行javaTest :
    这里写图片描述
    这里写图片描述


【2】访问静态域

1)访问静态域和访问非静态域类似。你要使用 GetStaticFieldID 和 GetStaticXxxField/SetStaticXxxField 函数。
2) 它们几乎与非静态的情形一样,只有两个区别(Distinctness):

  • D1)由于没有对象, 必须使用FindClass 代替 GetObjectClass 来获得类引用;
  • D2)访问域时, 要提供类而非实例对象;

3)看个荔枝: 下面给出的是怎样得到 System.out 的 引用代码:

/*  get the class */
jclass class_System = (*env)->FindClass(env, "java/lang/System"); 
/* get the field ID */
jfieldID id_out = (*env)->GetStaticFieldID(env, class_System, "out", "Ljava/io/PrintStream;");
/*  get the field value */
jobject obj_out = (*env)->GetStaticField(env, class_System, id_out);

【3】本地方法(JNI)——字符串参数(本节没有实际代码,故仅供了解吧,有机会再更新上代码)

(干货——字符串参数讲的是,怎样把字符串传入或传出本地方法)
1)problem+solution:

  • 1.1)problem: java中的字符串是 UTF-16 编码点的序列, 而 C 的字符串则是以 null 结尾的字节序列, 所以在这两种语言中的字符串是很不一样的;
  • 1.2)solution: java本地接口 有两组操作字符串的函数: 一组是把 java 字符串转换为 “改良的UTF-8”字节序列; 另一组将他们转换为 UTF-16 数值的数组, 也就是说转换为 jchar 数组;

Attention) 标准的UTF-8编码和 改良的UTF-8编码的区别:

  • A1) 标准UTF-8编码: 这些字符编码是4字节序列;
  • A2)改良的UTF-16 编码: 在改良的编码中, 这些字符首先被编码为 一对 UTF-16编码的替代品, 然后再对 每个替代品用 UTF-8 编码,总共产生6字节编码;
  • A3) 这有点笨拙, 但这是个历史原因造成的意外, 编写java 虚拟机规范的时候 Unicode 还局限在 16位;

2) jstring类型: 带有字符串参数的本地方法实际上都要接受一个 jstring 类型的值, 而带有字符串参数返回值的本地方法必须返回一个 jstring 类型的值。 JNI 函数将读入并构造出这些 jstring 对象;
3)看个荔枝(下面是对 NewStringUTF 函数的一个调用):

 JNIEXPORT jstring JNICALL Java_HelloNative_getGreeting(JNIEnv *env, jclass cl)
{
    jstring jstr;
    char greeting[] = "hello, native method. \n";
    jstr = (*env)->NewStringUTF(env, greeting);
    return jstr;
}

4) env指针: 所有对 JNI 函数的调用都使用到了 env 指针, 该指针是每个本地方法的第一个参数。

  • 4.1) env 指针定义: env指针是 指向函数指针表的指针(参见图12-2), 所以,你必须在每个JNI 调用前面加上 (*env)-> , 以便实际上解析对 函数指针的引用。 而且,env 是每一个JNI 函数的第一个参数;* (干货—— env 指针定义)
    这里写图片描述

5)NewStringUTF函数: 该函数从包含ASCII 字符的字符数组,或者更一般的“改良的UTF-8”编码的字节序列,创建一个新的 jstring;

  • 5.1) GetStringUTFChars 函数: 而读取现有 jstring 对象的内容,需要使用 GetStringUTFChars 函数, 该函数返回指向描述字符串的 “改良UTF-8”字符的 const jbyte* 指针;
  • 5.2)注意, 具体的虚拟机可以为其内部的字符串表示自由选择编码机制, 所以,你可以得到实际的 java 字符串的字符指针;
  • 5.3)另一方面: 如果虚拟机使用 UTF-16 或 UTF-32 字符作为其内部字符串的表示, 那么该函数会分配一个新的内存块来存储等价的 “改良的UTF-8”编码字符;

6) 垃圾回收器: 虚拟机必须知道你何时使用完字符串, 这样他就能够进行垃圾回收。垃圾回收器是在一个 独立线程中运行的, 他能够中断本地方法的执行; 基于这个原因, 你必须调用ReleaseStringUTFChars 函数;
7)另外: 可以通过调用 GetStringRegion 或 GetStringUTFRegion 方法来提供你自己的缓存, 以存放字符串的字符;
8)GetStringUTFLength函数:返回字符串的 “改良的UTF-8”编码所需的字符个数;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值