本地方法(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 類型的;
- 0.2)對于其他類型可以使用的函數有: GetIntField/SetIntField, GetObjectField/SetObjectField;其通用語法是: (干貨——這叫調用特殊的JNI函數來獲取和設置數據的值)
- 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 函數的一個調用):
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”編碼所需的字符個數;
總結
以上是生活随笔為你收集整理的本地方法(JNI)——访问域+字符串参数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 本地方法(JNI)——数值参数与返回值
- 下一篇: 上古卷轴5黎明符文战锤(上古卷轴5特殊的