【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”编码所需的字符个数;