1.Java平臺環境簡介:
"Java"平臺的編程環境包含"Java"虛擬機(VM)和 Java 應用程序編程的接口(Java Application Programming Interface(API))。"Java"應用程序是用"Java"編程語言編寫的,被編譯成一個獨立於 機器(machine-independent)二進制類格式.一個類在任何 Java 虛擬機上執行實現。Java 的 API 包 含預定義的類集合。Java"平臺的任何實現被假設支持 Java 編程語言,虛擬機和"API"。
主機環境(host environment)術語代表主機操作系統,本地庫組,和 CPU 指令集。用本地變成語 言(native programming languages)如"C"和"C++"編寫本地應用程序(Native application),被編譯特定主機的二進制編碼,和被連接到本地庫。本地應用程序和本地庫是典型依賴於特定主機環境, 爲一個操作系統建立的一個"C"應用程序。
"Java"平臺被一般的配置在主機環境的上面。例如,"JRE"(Java Runtime Enviroment)是"Sun"產品, 它支持"Java"平臺運行在例如"Solar is"和"Windows"的存在操作系統上。"Java"平臺提供一組特性,應用程序能依賴獨立於底下的主機環境。
JNI 的一個強大的特性是允許你來利用"Java"平臺,但利用的代碼仍然用其他語言來編寫。作爲"Java"虛擬機(Jave virtual machine)執行的一部分,"JNI"是一個雙向接口,允許 Java 應用程序調用本地代碼,反之亦然。
"JNI"被設計爲處理你需要聯合"Java"應用程序和本地代碼的情況。做爲一個雙向接口,"JNI"能支持兩種本地編碼:本地庫(native libraries)和本地應用程序(native application)。.你能使用"JNI"來編寫,允許 Java 應用程序來調用在本地庫中實現的函數的本地方法(native method)。"Java"應用程序,通過和他們調用在 Java 編程語言中實現的方法一樣辦法,調用本地 方法。然而幕後,本地方法是用另一種語言實現的和位於本地庫中。
2.JNI相關概要/基本類型/字符串/數組:
對於每個本地方法的實現的第一 個參數都是一個"JNIEnv"接口的指針第二個參數是對象自身的一個參考(在"C++"
中就像"this"指針)。
JNI的實質:就是對於程序來說需要傳遞參數給本地方法,和從本地方法中獲取結果。
- 類型的映射:在本地方法聲明中參數類型有對應本地編程語言中類型,JNI定義了一套C/C++類型來對應Java編程語言的類型,JNI傳遞objects到本地方法作爲不透明的引用,不透明引用是一個C類型的指針,引用了在Java虛擬機內部的數據結構。Java函數對JNIEnv接口指針式可用的,所有的JNI引用類型都是jobject;
- 訪問String:通過GetStringUTFChars()等類型轉換函數把Java中string類型映射成C/C++中的string類型,在不用字串要對其釋放如ReleaseStringUTFChars()函數。
Java_Prompt_getLine(JNIEvn *env, jobjext obj, jstring prompt) {
char buf[128] ;
const jbyte *str ;
str = (*env)->GetStringUTFChars(env, prompt , NULL) ;
if (NULL == str) {
return NULL;
}
printf("%s", str) ; (*env)->ReleaseStringUTFChars(env, prompt, str) ;
scanf("%s", buf) ;
return (*env)->NewStringUTF(env, buf) ;
}
- JNI拋出異常:JNI拋出異常不同於Java語言中拋出的異常,通過JNI拋出一個未決異常不自動改變在C/C++代碼中的控制流。替代是發佈一個清楚的返回聲明來在C/C++函數中跳過剩餘的語句。
- 訪問數組:數組通過jarray引用類型表示,Get/ReleaseIntArrayElements函數允許本地代碼來得到一個直接指向基本類型數組元素的指針。因爲底層的垃圾回收器可能不支持固定,虛擬器可能返回一個指向原始基本類型數組的一個副本。
Java_IntArray_sumArray(JNIEnv *env, jobjext obj, jintArray arr)
{
jint *carr ;
jint i, sum =0 ;
carr = (*env)->GetIntArrayElements(env, arr, NULL ) ;
if (carr == NULL){
return 0 ;
}
for (i = 0 ; i < 10 ; i++ ){
sum +=carr[i] ;
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0) ;
return sum ;
}
- 訪問Object類型的數組:"JNI"提供單獨一對函數來訪問"Objects"類型數組。"GetObjectArrayElement"函數返回被給的一個索引的元素,然而"SetOb jec tArrayElement "函數更新被給的一個索引的元素。不像基本類型數組情況,你不能立馬得到所有"object "類型的元素或複製多個"object"類型的元素。
Java_ObjextArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)
{
jobjectArray result;
int i ;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL){
return NULL ;
}
result = (*env)->NewObjectArray(env, size, intArrCls, NULL) ;
if (result == NULL ){
return NULL ;
}
for( i = 0 ; i < size ; i++){
jint tmp[256] ;
jint j ;
jintArray iarr = (*env)->NewIntArray(env, size) ;
if ( iarr == NULL){
return NULL ;
}
for (j = 0 ; j < size ; j++ ){
tmp[j] = i + j ;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp) ;
(*env)->SetObjectArrayElement(env, result, i, iarr) ;
(*env)->DeleteLocalRef(env, iarr) ;
}
return result ;
}
3.成員和方法(Fields and Methods):
Java語言中支持兩種類型的成員,實例成員域和靜態成員域。
JNI提供函數,本地代碼能使用用它來得到和設置在對象(objects)中的實例成員域和在類中的靜態成員域。
- 訪問成員域:一旦得到成員域的ID,就能傳遞對象引用和成員域給相應的實例域訪問函數:
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid ;
jstring jstr ;
const char *str ;
jclass cl = (*env)->GetObjectClass(env, obj) ;
printf("In C:\n") ;
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;") ;
if( fid == NULL ){
return ;
}
jstr = (*env)->GetObjectField(env, obj, fid) ;
str = (*env)->GetStringUTFChars(env, jstr, NULL) ;
if( str == NULL ){
return ;
}
printf(" c.s = "%s"\n", str ) ;
(*env)->ReleaseStringUTFChars(env, jstr, str) ;
jstr = (*env)->NewStringUTF(env, "123") ;
if( jstr == NULL ){
return ;
}
(*env)->SetObjectField(env, obj, fid, jstr) ;
}
- 成員域的描述符:一個類型引用的描述符如Java中的"java.lang.String"的成員域描述符在JNI中使用爲"Ljava/lang/String"。對於數組的的描述符包含字符"[",被數組的組成類型的描述符跟着如"[I"是"int[]"的成員域類型的描述。
- 訪問靜態成員域:使用GetStaticFieldID()函數,一旦得到靜態成員域ID相對於一個對象引用來給適當靜態成員域訪問函數。
- 訪問方法:"JNI"支持一整套函數來允許你執行來自本地代碼的回調。"JNI"是用的構造描述符字串來表示方法類型,如Java中的private String getString(String s)對應的是紅色爲返回類型:"(Ljava/ lang/String;) Ljava/ lang/String; "
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj) ;
jmethod mid = (*env)->GetMethodID(env, cls, "callback", "()V") ;
if(mid == NULL){
return;
}
printf("In C\n") ;
(*env)->CallVoidMethod(env, obj, mid) ;
}
- 調用靜態方法:用GetStaticMethodID()函數,在允許你調用的靜態方法的函數和允許你調用實例方法的函數之間有一個關鍵的不同點。前者(former)調用一個類的引用(class reference)作爲第二個參數,然而後者(latter)使用一個對象應用(object reference)作爲第二個參數。
- 調用一個超類的實例方法:
- 調用構造器:可以使用"Ca llNonv irtua lVo idMethod "函數來調用構造器。
- 緩衝成員域和方法ID:得到成員域和方法"ID"需要基於名字和成員域或方法的描述符的符號的查找。符號的查找是相對比較是費時的。
使用時緩衝:成員域和方法 ID 可以被緩衝,在本地代碼訪問成員域值或執行方法回調的地方:
Java_InstanceFieldAccess_accessField(JNIEnv *env,jobject obj)
{
static jfieldID fid_s = NULL ;
jstring jstr ;
const char *str ;
if (fid_s == NULL ){
fid_s = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;") ;
if( fid_s == NULL) {
return ;
}
}
printf("In C:\n") ;
jstr = (*env)->GetObjectField(env, obj, fid_s) ;
str = (*env)->GetStringUTFChar(env, jstr, NULL) ;
if ( str == NULL ){
return ;
}
printf(" c.s = "%s"\n", str) ;
(*env)->ReleaseStringUTFChars(env, jstr, str) ;
jstr = (*env)->NewStringUTF(env, "123") ;
if( jstr == NULL){
return ;
}
(*env)->SetObjectField(env, obj, fid_s, jstr) ;
}
在定義類的初始化中緩衝:引入本地方法“initIDs”,在Java類靜態初始化中調用。
Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) {
MID_InstanceMethodCall_callback = (*env)->GetMethodID(env, cls, "callback", "()V") ;
}
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
printf("In C\n") ;
(*env)->CallVoidMethod(env, obj, MID_InstanceMethodCall_callback) ;
}
4.局部和全局引用:
"JNI"支持三種類型不透明引用:局部引用,全局引用和弱全局引用。
局部和全局引用有不同的生命週期(lifet imes)。局部引用會自動釋放,然而全局和弱全局引用保持有效一直到它們被程序員釋放。
一個局部或全局的引用保持了引用的對象(referenced object)不被垃圾收集掉。另一方面,一個弱全局引用允許引用的對象被垃圾收集。
- 局部引用:一個局部引用,只在創建它的本地方法的動態上下文中,同時只在本地方法的一個調用中是有效的。所有的在一個本地方法執行期間創建的局部引用將被釋放,一旦本地方法返回;
- 全局引用:一個全局引用能在多個線程中(across muliple threads)被使用,同時保持有效直到編程者釋放它。像一個局部引用,一個全局引用確保 了引用對象將不被垃圾收集。全局引用只被一個JNI函數"NewGlobalRef"創建。
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
static jclass stringClass = NULL ;
if ( stringClass == NULL ){
jclass LocalRefCls = (*env)->FindClass(env, "java/lang/String") ;
if (localRefCls == NULL ){
return NULL ;
}
stringClass = (*env)->NewGlobalRef(env, localRefCls) ;
(*env)->DeleteLocalRef(env, localRefClas) ;
if( stringclass == NULL ){
return NULL ;
}
}
}
- 弱全局引用:使用"NewGlobalWeakRef"來創建和使 用"DeleteGlobalWeakRef"來釋放,弱全局引用不能保持底層對象不被垃圾收集。
- 引用比較:使用"IsSameObject"函數,一個在"JNI"中的"NULL"引用參考"null"對象,對於弱引用只能參考 "nu ll"對象。
(*env)->IsSameObject(env, obj1, obj2);
(*env)->IsSameObject(env, wobj, NULL);
- 釋放引用:
釋放局部引用:一般垃圾回收器會自動回收,但有時創建太多時也要自己去釋放,"JNI"說明指示虛擬器自動地確保每個本地方法能創建至少 16 個局部引用。
(*env)->DeleteLocalRef(env, jstr) ;
釋放全局和弱引用:當你的本地代碼不再需要訪問全局引用時,你應該調用"DeleteGlobalRef"。如果你調用這個函數失敗,"Java"虛擬器將不能垃圾收集這對應的對象,即使在系統的任何地方當對象不再使用的時候。
當你的本地代碼不再需要訪問一個弱全局引用,你應該調用"DeleteWeakGlobalRef"。如果你調 用這個函數失敗,"Java"虛擬器任然將能垃圾收回底層對象,但將不能收回(reclaim)被弱全局應 用自己消耗的內存。
5.異常:
- 在本地方法中緩衝和拋出異常:"CatchThrow "類聲明一個"doit"本地方 法,同時指定他拋出一個"IllegalArgumentException"。
Java_CatchThrow_doit(JNIEnv *env, jobject obj)
{
jthrowable exc ;
jclass cls = (*env)->GetObjectClass(env, obj) ;
jmethodID mid =
(*env)->GetMethodID(env, cls, "callback", "()V") ;
if ( mid == NULL ){
return ;
}
(*env)->CallVoidMethod(env, obj, mid) ;
exc = (*env)->ExceptionOccurred(env) ;
if (exc){
jclass newExcCls ;
(*env)->ExceptionDescribe(env) ;
(*env)->ExceptionClear(env) ;
newExcCls = (*env)->FindClass(env,
"java/lang/Illega lArgument Exception ");
if( newExcCls == NULL ){
return ;
}
(*env)->ThrowNew(env, newExCls, "thrown from C code") ;
}
}
- 檢查異常:有兩種方法檢查是否有錯誤發生。
大多"JNI"函數使用一個清晰的(distinct)返回值(例如 NULL)來指示一個錯誤發生。錯誤返回值也暗示在當前線程有個未解決的異常;
當使用一個"JNI"函數的返回值不能標記一個錯誤發生時,本地代碼必須依賴產生異常來做錯 誤檢查。在當前線程中執行一個未決異常檢測的"JNI"函數是"ExceptionOccurred"。
void f(JNIEnv *env, jobject fraction) {
jint floor = (*env)->CallIntMethoed(env, fraction, MID_Fraction_floor) ;
if((*env)->ExceptionCheck(env)){
return ;
}
}
- 處理異常:本地代碼可以處理一個未決的異常,本地方法實現能選擇立即返回,引起異常在調用者中處理它;本地代碼通過調用"ExceptionClear"能清理異常,然後執行它自己的異常處理代碼。
- 在工具函數中的異常:
6.調用接口:
一個 Java 虛擬器實現是典型 作爲一個本地庫的運用。本地應用程序能針對這個庫鏈接和使用載入 Java 虛擬機的調用接口。
這代碼條件編譯一個初始化結構"JDK1_1InitArgs",這結構明確虛擬器在"JDK release 1.1"上實現:
#include <jni.h>
#define PATH_SEPERATOR ';‘
#define PATH_CLASSPATH '.'
main(){
JNIEnv *env ;
JavaVM *jvm ;
jint res ;
jclass cls ;
jmethodID mid ;
jstring jstr ;
jclass stringClass ;
jobjectArray args ;
#ifdef JNI_VERSIO_1_2
JavaVMInitArgs vm_args ;
JavaVMOption options[1] ;
options[0].optionString = "-Djava.class.path="USERCLASSPATH ;
vm_args.version = 0x00010002 ;
vm_args.options = options ;
vm_args.ignoreUnrecognized= JNI_TRUE ;
res = JNI_CreateJavaVM(&jvm, (Void **)&env, &vm_args) ;
#else
JDK1_1InitArgs vm_args ;
char classpath[1024] ;
vm_args.version = 0x00010001 ;
JNI_GetDefaultJavaVMInitArgs(&vm_args) ;
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPERATOR, USER_CLASSPATH) ;
vm_args.classpath = classpath ;
res = JNI_CreateJavaVM(&jvm, &env, &vm_args) ;
#endif
if ( res < 0 ){
fprintf(stderr, "Can't create Java VM\n") ;
exit(1) ;
}
cls = (*env)->FindClass(env, "Prog") ;
if ( cls == NULL ){
goto destroy ;
}
mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V") ;
if ( mid == NULL ){
goto destory ;
}
jstr = (*env)->NewStringUTF(env, " From C!") ;
if( jstr == NULL ){
goto destory ;
}
stringClass = (*env)->FindClass(env,"java/lang/String") ;
args = (*env)->NewObjectArray(env, 1, stringClass, jstr) ;
if( args == NULL ){
goto destory ;
}
(*env)->CallStaticVoidMethod(env, cls, mid, args) ;
destroy:
if( (*env)->ExceptionOccurred(env) ){
(*env)->ExceptionDescribe(env) ;
}
(*jvm)->DestroyJavaVM(jvm) ;
}