JNI中的數據類型以及數據結構
一、概述
因爲JNI是一套Native層和JVM通信的機制,通信就是信息交流,所以就必須要提供在兩種語言間的數據交換的機制。基於這個原因,JNI中通過typedef定義了一套專用的數據類型以及數據結構。
二、Java基本類型
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | not applicable |
還添加了一下定義方便使用
#define JNI_FALSE 0
#define JNI_TRUE 1
typedef jint jsize
用於標識索引和大小(C++標準建議用盡量準確的類型符來標識一個變量。比如,表示大小就用size_t等,而不是使用在個平臺都有可能產生不一致的int、short等)
三、Java引用類型
JNI 中還包含了一組特殊的類型,用來對應Java中的引用類型,我們暫且稱之爲JNI引用類型。爲了儘量和Java引用類型像照應,JNI引用類型遵照如下層次結構:
- jobject(用於引用所有Object對象)
- jclass(用於引用Classs對象)
- jstring(用於引用String對象)
- jarray(數組對象)
- jobjectArray(對象數組)
- jbooleanArray(boolean數組,不是Boolean數組)
- jbyteArray(byte數組)
- jcharArray(char數組)
- jshortArray(short數組)
- jintArray(int數組)
- jlongArray()
- jfloatArray()
- jdoubleArray()
JNI在定義這一組JNI引用類型的時候,正對C和C++分別採取了不同的定義方式。C中採用typedef jobject jclass;
這種定義。C++因爲是面向對象的,所以直接採用了類繼承的方式。詳見下圖
可以看到在C中,不管是什麼類型本質上都是void* 類型,而C++則是空類並且有了繼承關係。可本質上並沒有什麼區別,使用的時候具體是什麼類型仍需要用戶自己去區別。
四、Java字段和方法(Field/Method)
Java中的字段和方法定義爲C中結構體的指針,如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
jfieldID以及jmethodID後面註釋了一句 “opaque structure(不透明結構)”,應該是說該結構交給JVM自己去定義。jni.h文件中還定義了JNINativeMethod類型,該類型暫時不用關注,它用於標識一個Java方法(一般在Native方法與Java方法的動態綁定時使用)。
五、JNI Value類型(聯合體類型)
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
六、JNI中的類型簽名
在JNI中個JVM通信時很多時候不使用具體類型(比如boolean、int、class等)而是使用類型簽名,並且是直接使用JVM所使用的類型簽名(應該是處於效率之類的考慮吧)。JVM中的類型簽名如下:
類型簽名 | 具體Java類型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L 接上具體Class的全限定名 ; | 具體的Class類型 |
[ 接上類型簽名 | 數組類型 |
(參數簽名)方法返回值類型簽名 | 方法類型簽名 |
舉使用類型簽名的一個例子:
一個Java方法:
long f(int n,String s,int[] arr);
其類型簽名如下:
(ILjava/lang/String;[I)J
注意其中的信息:I 是 int 的類型簽名,Ljava/lang/String; 是 String 的類型簽名,[I 是 int[] 的類型簽名,J是long的類型簽名。* 其合在一起是
long f(int n,String s,int[] arr);
*這個方法的類型簽名。Java中有方法簽名來標識方法的唯一性,那麼就有類型簽名來標識類型的唯一性(類型簽名就是給boolean、int、class等這些找了一個簡稱或者別稱)。
其中最後一個方法類型簽名,在java.lang.invoke包中有一個MethodType類型,該類型屬於Java方法簽名的一部分,包含方法的參數信息以及返回值信息。我們可以理解爲,方法包含類型信息以及方法名信息(類似參數包含參數名以及類型信息)。
七、JNI中UTF-8字符串說明
JNI中使用的字符編碼方案是在UTF-8的基礎上稍加修改而來的,具體的查看JNI文檔(JVM中採用的也是修改過的UTF-8)。
其中涉及到兩點不同:
- 對空字符
(char)0
編碼時不是像標準UTF-8那樣使用一個字節,而是使用兩個字節,這就避免了在字符串中嵌入了空值(應該是避免了無意間引入的空字符對JVM的運行產生影響)。 - Java VM 無法識別UTF-8的四字節格式,它使用兩倍或三倍三字節來代替UTF-8中超過三字節的表示。
八、字符編碼說明
因爲早前搞OpenVPN的時候仔細去熟悉了下Unicode纔算明白Unicode以及UTF-8/UTF-16之間的淵源,簡單的提一下。
Unicode(統一碼、萬國碼、單一碼),是計算機行業定義的一套字符標準。一般我們提到的時候會說Unicode字符集,講的是一套字符集,而不是一套編碼方案,並且它引入了平面的概念。不講複雜碼位平面什麼的,簡單點說Unicode字符集用四個字節來指向一個字符,這是字符標準,在你電腦的字體文件中就是通過Unicode的碼位來一一照應上文字(不是通過UTF-8或者其他編碼方案的值)。而UTF-8/UTF-16等都編碼方案,將四個字節的標準Unicode碼編碼成UTF-8等所定義的編碼格式,但是真正用的時候還需要解碼回Unicode。之所以這樣做,是因爲Unicode每個字符都佔用4個字節,但常用字符往往沒那麼多,重新實現編碼方案將大大的縮小空間浪費。
九、JNI爲什麼定義這些信息
JNI是JVM和Native層溝通的橋樑,要達到JVM和Native可以相互溝通的目的,就必須實現下面兩點
- Java中可以調用Native層的方法(不可避免的需要傳遞參數),持有Native層數據結構或者對象的引用,保存Native層的變量。
- Native同樣需要可以調用Java層的方法,持有Java層對象的引用,保存Java層變量。
1、基於方法調用
- 在Java層提供了native方法屬性,被該屬性標記的方法可以和Native層方法產生關聯用來代用Native層方法。關聯方式有:
- 靜態綁定(通過命名方式綁定)
- 動態綁定(通過手動寫綁定方法綁定,後面會有)
- 在Native層呢,就是通過上面的jclass、jmethod等以及方法類型簽名來調用。
2、相互保存對方變量以及持有對方引用
- Java層持有Native層對象一般很好解決,真毒C/C++來說,直接保存其內存地址或者內存數據即可。
- Native層只有Java層對象只能通過jclass、jstring、jobjectArray等來持有對象的引用。數據的保存,比如byte[]等數組類型除了持有引用外還提供一些額外的操作,像copy等,後面會有介紹。