轉自:http://wxiaolei.blog.163.com/blog/static/10387601200841762812328/
通過本章可以瞭解c文件如何使用java複雜的數據類型。
從Java 1.1開始,Java Native Interface (JNI)標準成爲java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是爲了本地已編譯語言,尤其是C和C++而設計 的,但是它並不妨礙你使用其他語言,只要調用約定受支持就可以了。讓我們看一些使用JNI的簡單例子吧。
使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬件、操作系統進行交互,或者爲了提高程序的性能。JNI標準至少保證本地代碼能工作在任何Java 虛擬機實現下。
開始:
如果你習慣了使用JNI,你就不會覺得它難了。既然本地方法是由其他語言實現的,它們在Java中沒有函數體。但是,所有本地代碼必須用本地關鍵詞 聲明,成爲Java類的成員。清單A演示了一個簡單的類,它申明瞭一個本地的(native),靜態的(static)方法:sum。
寫完了你的Java類,接下來就要寫本地代碼。本地方法符號提供一個滿足約定的頭文件,使用Java工具可以很容易地創建它而不用手動去創建。你對 Java的class文件使用javah命令,就會爲你生成一個對應的C/C++頭文件。清單B就是爲清單A的Test1類創建的頭文件。注意:它創建了 一個C/C++函數:Java_Test1_sum。
執行本地方法:
一旦你有了這個頭文件,你就需要寫頭文件對應的本地方法,就像我在清單C做的那樣。注意:所有的本地方法的第一個參數都是指向JNIEnv結構的。 這個結構是用來調用JNI函數的,(我會在另一個章節中討論)。第二個參數jclass的意義,要看方法是不是靜態的(static)或者實例 (Instance)的。前者,jclass代表一個類對象的引用,而後者是被調用的方法所屬對象的引用。最後的兩個jint參數表示了Java方法的 int參數。
返回值和參數類型根據等價約定映射到本地C/C++類型,如表A所示。有些類型,如清單B裏面的兩個jint參數,在本地代碼中可直接使用,而其他類型只有通過JNI調用操作。
表A
Java類型 | 本地類型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號的8位整型 |
char | jchar | C/C++無符號的16位整型 |
short | jshort | C/C++帶符號的16位整型 |
int | jint | C/C++帶符號的32位整型 |
long | jlong | C/C++帶符號的64位整型e |
float | jfloat | C/C++32位浮點型 |
double | jdouble | C/C++64位浮點型 |
Object | jobject | 任何Java對象,或者沒有對應java類型的對象 |
Class | jclass | Class對象 |
String | jstring | 字符串對象 |
Object[] | jobjectArray | 任何對象的數組 |
boolean[] | jbooleanArray | 布爾型數組 |
byte[] | jbyteArray | 比特型數組 |
char[] | jcharArray | 字符型數組 |
short[] | jshortArray | 短整型數組 |
int[] | jintArray | 整型數組 |
long[] | jlongArray | 長整型數組 |
float[] | jfloatArray | 浮點型數組 |
double[] | jdoubleArray | 雙浮點型數組 |
※ JNI類型映射
最後一步是把本地代碼編譯成共享庫(比如,UNIX的so文件,Windows的dll文件)。在Java中調用方法前,共享庫須通過System.loadLibrary導入。最常用的方式是在類的靜態(static)初始化器裏做這這個工作。
在本地代碼中訪問JNI
我舉的例子很簡單,並不能滿足演示怎樣寫JNI方法的目標。現在,讓我們看一些高級的,通過JNIEnv結構使用非簡單類型的例子。
JNI通過函數的形式提供了很多功能,供本地代碼通過指向JNIEnv結構的指針調用;它作爲第一個參數傳遞給每個本地方法。JNI函數的調用有下面幾種格式(這裏,假設env是指向JNIEnv的指針):
//C 格式
(*env)-><jni function>( env, <parameters> )
//C++ 格式
env-><jni function>( < parameters> )
這篇文章中接下來的例子我將會用C++格式。
使用數組:
JNI通過JNIEnv提供的操作Java數組的功能。它提供了兩個函數:一個是操作java的簡單型數組的,另一個是操作對象類型數組的。
因爲速度的原因,簡單類型的數組作爲指向本地類型的指針暴露給本地代碼。因此,它們能作爲常規的數組存取。這個指針是指向實際的Java數組或者Java數組的拷貝的指針。另外,數組的佈置保證匹配本地類型。
爲了存取Java簡單類型的數組,你就要要使用GetXXXArrayElements函數(見表B),XXX代表了數組的類型。這個函數把Java數組看成參數,返回一個指向對應的本地類型的數組的指針。
表B
函數 | Java數組類型 | 本地類型 |
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
JNI數組存取函數
當你對數組的存取完成後,要確保調用相應的ReleaseXXXArrayElements函數,參數是對應Java數組和 GetXXXArrayElements返回的指針。如果必要的話,這個釋放函數會複製你做的任何變化(這樣它們就反射到java數組),然後釋放所有相 關的資源。
爲了使用java對象的數組,你必須使用GetObjectArrayElement函數和SetObjectArrayElement函數,分別去get,set數組的元素。GetArrayLength函數會返回數組的長度。
清單D包含了一個簡單的類,它演示了本地代碼如何使用Java數組。這個本地實現循環遍歷一個整型(int)數組,返回這些元素的總和。爲簡單起見,這個清單包含了java代碼和本地實現。我已經省略了頭文件,它可以很方便地通過javah得到。
在本地代碼中訪問JNI
使用對象
詳細請參照:Jni學習三:jni使用對象詳解
JNI提供的另外一個功能是在本地代碼中使用Java對象。通過使用合適的JNI函數,你可以創建Java對象,get、set 靜態(static)和實例(instance)的域,調用靜態(static)和實例(instance)函數。JNI通過ID識別域和方法,一個域或 方法的ID是任何處理域和方法的函數的必須參數。
表C列出了用以得到靜態(static)和實例(instance)的域與方法的JNI函數。每個函數接受(作爲參數)域或方法的類,它們的名稱,符號和它們對應返回的jfieldID或jmethodID。
表C
函數 | 描述 |
GetFieldID | 得到一個實例的域的ID |
GetStaticFieldID | 得到一個靜態的域的ID |
GetMethodID | 得到一個實例的方法的ID |
GetStaticMethodID | 得到一個靜態方法的ID |
※域和方法的函數
如果你有了一個類的實例,它就可以通過方法GetObjectClass得到,或者如果你沒有這個類的實例,可以通過FindClass得到。符號是從域的類型或者方法的參數,返回值得到字符串,如表D所示。
表D
Java 類型 | 符號 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects對象 | Lfully-qualified-class-name;L類名 |
Arrays數組 | [array-type [數組類型 |
methods方法 | (argument-types)return-type(參數類型)返回類型 |
※確定域和方法的符號
一旦你有了類和方法或者域的ID,你就能把它保存下來以後使用,而沒有必要重複去獲取。
有幾個分別訪問域和方法的函數。實例的域可以使用對應域的GetXXXField的變體函數訪問。GetStaticXXXField函數用於靜態類型。設置域的值,用SetXXXField 和SetStaticXXXField函數。表E包含了所有訪問域的函數列表。
表E
Java 類型 | Method方法 |
boolean | GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField |
byte | GetByteField, GetStaticByteField, SetByteField, SetStaticByteField |
char | GetCharField, GetStaticCharField, SetCharField, SetStaticCharField |
short | GetShortField, GetStaticShortField, SetShortField, SetStaticShortField |
int | GetIntField, GetStaticIntField, SetIntField, SetStaticIntField |
long | GetLongField, GetStaticLongField, SetLongField, SetStaticLongField |
float | GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField |
double | GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField |
object | GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField |
※訪問域的函數
另外,方法的訪問是由CallXXXMethod 函數和CallStaticXXXMethod函數完成的,XXX表明了方法的返回值類型。這些函數的變體允許傳遞數組參數 (CallXXXMethodA and CallStaticXXXMethodA)或者傳遞一個可變大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。
一個完整的列表
表F:一個完整的列表
返回類型 | 函數 |
boolean | CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV |
byte | CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV |
char | CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV |
short | CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV |
int | CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV |
long | CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV |
float | CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV |
double | CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV |
void | CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV |
object | CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV |
※方法訪問函數
清單E演示瞭如何在本地代碼中調用方法。本地方法printRandom得到了靜態方法Math.random的ID,並且調用它幾次,打印出結果。實例方法也一樣處理。