Java 本地接口(JNI)編程指南和規範學習筆記2

1.JNI和線程:

Java 虛擬器支持控制併發的在一樣地址空間中執行的多線程,多線程可以訪問同一個對象,同一個文件描述符。

多線程的限制:

  • 一個"JNIEnv"指針只在和其關聯的線程中有效。不必傳遞這個指針從一個線程到另一個線程, 或者在多線程中緩衝和使用它。在從同一個線程的併發調用中,"Java"虛擬器傳遞給一個本地方法同樣的"JNIEnv"指針;但當從不同線程中調用那個本地方法時,傳遞不同的"JNIEnv"指針。 避免在一個線程緩衝"JNIEnv"指針和在另一線程中使用這個指針的一般性錯誤。
  • 局部引用只在創建它們的線程中是有效的。不必傳遞局部引用從一個線程到另一個線程。應該總是轉換局部引用爲全局引用,任何時候多線程可以使用一樣的。

監視入口和監視出口:

Java中的同步機制,每個對象能被動態地關聯到一個監視上。Java虛擬器保證了在線程執行塊中的任何語句前,線程獲得和“obj”對象關聯的監視。這確保在給定的任何時候在這至多有一個線程擁有監視器和在同步塊中執行,當一個線程等待另一個線程退出監視時,這線程阻塞:

synchronized(obj){
    // synchronized block 
}

JNI中本地代碼使用JNI函數來實現同步,使用"MonitorEnter"函數來進入監視和"MonitorExit"函數來退出監視,調用"Mon itorExit "的失敗將幾乎可能導致死鎖,所以在次最好作出異常處理:

if((*env)->MonitorEnter(env, obj) != JNI_OK);

if ((*env)->ExceptionOccurred(env)){ 
    if ((*env)->MonitorExit(env, obj) != JNI_OK);
 }

監視的等待和通知:

在Java的提供"Object.wait","Object.notify","Object.notifyAll"的同步相關的方法;而JNI沒有提供類似的方法,但可以本地方法調用Java來去實現:

static jmethodID MID_Object_wait ;
static jmehtodID MID_Object_notify ;
static jmethodID MID_Object_notifyAll ;

void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout) {
    (*env)->CallLongMethod(env, object, MID_Object_wait, timeout) ; 
}

void JNU_MonitorNotify(JNIEnv *env, jobject object) {
    (*env)->CallVoidMethod(env, object, MID_Object_notify) ; 
}

void JNU_MonitorNotifyAll(JNIEnv *env, jobject object) {
    (*env)->CallVoidMethod(env, object, MID_Object_notifyAll) ;
}

在任意上下文獲取一個指針“*JNIEnv”:

"JNIEnv"指針只在它關聯的線程中有效,當不能從虛擬器被直接調用來得到屬於當前線程的"JNIEnv"接口指針。通過"AttachCurrentThread"函數的接口調用來獲取:

獲取Java VM指針的方法:

  • 通過在創建虛擬器的時候記錄;
  • 通過使用"JNI_GetCreateJavaVMs"來查詢被創建的虛擬器;
  • 通過在一個一般的本地方法中調用"JNI"函數"GetJavaVM";
  • 或者通過定義一個"JNI_OnLoad"處理。
JavaVM *jvm
f() {
    JNIEnv *env ;
    (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL ) ; 
}

 匹配線程模型:

如果"Java"虛擬器實現支持一種線程模型,且線程模型匹配了通過本地代碼的虛擬器,那 時所有這些方法(approach)都能工作。線程模型(thread model)指示(dictat)系統怎樣實現必要的線 程操作,例如時序安排(scheduling),上下文的切換(context switching),同步(sychronization),和 在系統調用中的阻塞(blocking)。另一方面,在一個用戶的線程模型中,應用程序代碼實現了線程的操作。

 

2.字符串string的相關操作:

  • 從本地字符串創建"jstring":

使用"string(byte[] bytes)"構造器(constructor)來轉換一個本地字符串爲一個"jstring",MID_String_init是字符構造器的方法ID。

jstring JNU_NewStringNative(JNIEnv *env, const char *str)
{
    jstring result ;
    jbyteArray bytes = 0 ; 
    int len ;
    if((*env)->EnsureLocalCapacity(env, 2)< 0 ){ 
        return NULL ;
    }
    len = strlen(str) ;
    bytes = (*env)->NewByteArray(env, len) ; 
    if (bytes != NULL ){
        (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *)str) ; 
        result = (*env)->NewObject(env, Classjava_lang_String,
            MID_String_init, bytes) ;
        (*env)->DeleteLocalRef(env, bytes) ; 
        return result ;
    }
}
  •  轉換jstring到本地字符串:

使用"String.getBytes"方法來轉換一個"jstring"爲恰當的本地編碼。

char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
    jbyteArray bytes = 0 ; 
    jthrowable exc ;
    char *result = 0 ;
    if( (*env)->EnsureLocalCapacity(env, 2) < 0 ){ 
        return 0 ;
    }
    bytes = (*env)->CallObjectMethod(env, jstr, MID_String_getBytes) ;
    exc = (*env)->ExceptionOcurrend(env) ; 
    if(!exc){
        jint len = (*env)->GetArrayLength(env, bytes) ; 
        result = (char *)malloc(len+1) ;
        if (result == 0 ){
            JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0 ) ; 
            (*env)->DeleteLocalRef(env, bytes) ;
            return 0 ;
        }
        (*env)->GetByteArrayRegion(env, bytes, 0, len, (jbyte *)result) ;                
        result[len] = 0 ;
    } else{
        (*env)->DeleteLocalRef(env, exc) ; 
    }
    (*env)->DeleteLocalRef(env, bytes) ;
    return result ; 
}

 

3.註冊本地方法:

"JNI"編程者能手動地(manually )鏈接本地函數,通過註冊帶有一個類引用,方法名字和方法描 述符的一個函數指針。

JNINativeMethod nm;
nm.name = "g";
nm.signature = "()V" ;
nm.fnPtr = g_impl ; 
(*env)->RegisterNatives(env, cls, *nm, 1) ;
//代碼註冊本地函數"g_impl"做爲"Foo.g"本地方法的實現
void JNICALL g_impl(JNIEnv *env, jobject self) ;

 

4.載入和載出處理程序:

載入和載出處理程序允許本地庫導出兩個函數:一個在 "System.loadLibrary"載入本地庫時調用,另一個在虛擬器載出本地庫時調用。

  • JNI_OnLoad 處理程序:

當"System.loadLibrary"載入一個本地庫時,虛擬器在本地庫中搜索下面導出入口

JNIEXPORT jint JINCALL JNI_OnLoad(JavaVM *jvm, void *reserved) ;

在JNI_OnLoad的實現中,能調用任何"JNI"函數。"JNI_OnLoad "處理程序的典型使用時緩衝"JavaVM"指針,類的引用,或者方法和域的"IDs"

JavaVM *cached_jvm ;
jclass Class_C ;
jmethodID MID_C_g ;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    JNIEnv *env ; 
    jclass cls ;
    cached_jvm = jvm ;
    if((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)){
        return JNI_ERR ;
    }
    cls = (*env)->FindClass(env, "C") ; 
    if( cls == NULL ){
        return JNI_ERR ; 
    }
    Class_C = (*env)->NewWeakGlobalRef(env, cls) ; 
    if(Class_C == NULL ){
        return JNI_ERR ; 
    }
    MID_C_g = (*env)->GetMethodID(env, cls, "g","()V") ; 
    if(MID_C_g == NULL ){
        return JNI_ERR ; 
    }
    return JNI_VERSION_1_2 ; 
}
  • JNI_OnUnload 處理程序:

載出本地庫的規則是如下:

虛擬器關聯每個本地庫使用"class C"的類載入器"L"調用了"System.loadLibrary"函數;

在它決定類載入器"L"不在是一個活的對象(a live object)後,虛擬器調用"JNI_OnUnload"處理, 同時載出本地庫。因爲一個類載入器查看了這個虛擬器定義的所有的類,這暗示(imply)類 C 也 能被載出;

"JNI_OnUn load "處理程序在最後運行,且被 "java.lang.System.runFinalization"同步地調用或者被 虛擬器同步地調用。

JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved)
{
    JNIEnv *env ;
    if((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)){ 
        return ;
    }
    (*env)->DeleteWeakGlobalRef(env, Class_C) ;
    return ; 
}
  • 反射支持(Reflection Support):

反饋允許你在運行(at run time)時發現任何類對象,系列域和在類中定義的方法的名字。在 Java 編程語言級通過"java.lang.reflect"包來提供反射的支持,和在"java. lang.Object"和"java.lang.Class"類中的一些方法一樣。雖然你總能調用對應的"Java API"來執行(carry out)反饋操作,"JNI"提供下面函數使來自本地代碼的頻繁的反饋操作更有效和方便:

  1. "GetSuperclass"返回一個被給類引用的父類;
  2. "IsAssignableFrom"檢查一個類的實體是否能被用,當另一個類的事例期待使用時;
  3. "GetObjectClass"返回被給"jobject"引用的類;
  4. "IsInstanceOf "檢查一個"jobject"對象是否是一個被給類的實體;
  5. "FromReflectedField and ToReflectedField"允許本地代碼在域"ID"和"java.lang.reflect.Field"對象之間轉換;
  6. "FromReflectedMethod and ToReflectedMethod"允許本地代碼在方法"IDs","java.lang.reflect.Method objects"和"java.lang.reflect.Constructor objects"之間轉換。
  • JNI 在 C++中的編程:
jclass cls = env->FindClass("java/lang/String");
//C中的調用在用以上C++的代替:
jclass cls = (*env)->FindClass(env, "java/lang/String");

"jni.h"文件也定義一些列空的"C++"類來強制在不同的"jobject"子類型中子類化聯繫:

// JNI reference type defined in C++
class _jobject{} ;
class _jclass: public _jobject{} ; 
class _jstring: public _jobject{} ; 
typedef _jobject * jobject ;
typedef _jclass* jclass ;
typedef _jstring* jstring ;

在 C++中加入類型的層次(type hirrarchy)有時額外的轉換(casting)成必要(necessitate):

jstring jstr = (*env)->GetObjectArrayElement(env, arr, i) ;
//在"C++"中你需要插入一個清晰的轉換
jstring jstr = (jstring)env->GetObjectArrayElement(arr, i) ;

 

5.利用存在的本地庫:

  • 一對一映射(Java的方法對應JNI唯一的函數):

一對一的映射方法(approach)需要你來寫一個存根函數爲打包的每個本地函數。

在一對一映射 ,存根的(stub)函數有兩個目的:

  1. 存根函數使本地函數的參數傳遞的協定適合"Java"虛擬器的期望。虛擬器期望本地方法實現爲 一個被給地命名協定和接受倆個額外的參數("JNIEnv"指針和"this"指針);
  2. 存根函數在"Java"編程語言類型和本地類型之間轉換。例如,"Java_Win32_CreateFile"函數轉換"js tring"文件名爲一個本地描述的 "C "字符串。
  • 共享存根(把一系列的本地方法調用實現封裝在Java的類中):

一個共享存根是一個本地函數,這函數分配給其他本地函數。共享存根負責轉換來自調用者提供的參數類型到本地函數能夠接受的參數類型。

  • 一對一映射與共享存根相對:

一對一的映射和共享存根是兩種爲本地庫建立封裝類的方法,每個方法有它自己優點。共享存根的主要優點是程序員不需要寫在本地代碼中寫大量的存根函數;一對一映射的優點是典型地在轉換數據類型中更有效率,數據是在"Java"虛擬器和本地代碼(native code)之間傳遞。

  • 共享存根的實現:

 

6.陷阱和缺陷:

  • 檢查錯誤:

"JNI"不能依賴任何特殊的本地異常機制(例如"C++"異常)。 因此,在每個可能產生一個異常的"JNI"函數調用後 ,編程者被要求執行清楚地檢查;

  • 傳送無效參數給"JNI"函數:

"JNI"函數不會嘗試檢查(detect)和恢復(recorer from)無效參數;

  • 混淆"jclass"和"jobject":

實例的引用對應"java.lang.Object"或它的子類的一種的實例和數組。類引用對應"java.lang.Class"實例,代表類的類型(class types);

  • 截短"jboolean"參數:

一個"jboolean"是一個"8-bit"無符號"C"類型,它能存儲 0 到 255 的值。0 值對應常數"JNI_FALSE",1到 255 的值對應"JNI_TRUE"。但"32-bit or 16-bit"大於 255 的值,它的低"8 bits"是 0 時,造成(pose)一個問題;

  • "Java"應用和本地代碼之間的邊界線:

保持邊界簡單;在本地代碼方保持代碼盡力少;保持本地代碼獨立;

  • 混淆 IDs 和引用:

"JNI"揭示對象(objects)爲引用。類,字符串和數組(as references, Classes, strings, and arrays)是引用的特別類型。"JNI"方法和成員域爲 IDs。一個 ID 不是一個引用。不能認爲一個類的引用(class reference)是一個"Class ID",或者一個方法 ID 是一個"方法應用(method reference)"。

  • 緩衝成員域和方法 IDs:

本地代碼通過指定的成員域或方法的名字和類型描述符作爲字符串,從虛擬器得到成員域或方法"IDs"。成員域和方法查看使用的名字和類型字符串是很慢的。通常爲解決這問題來緩衝"IDs"。在本地代碼中,緩衝成員域和方法 ID 的失敗是一種常見的性能問題。

  • Unicode 字符串的結束:
  • 從"GetStringChars or GetStringCritical"得到 Unicode 字符串是沒有"NULL"結束的(NULL-terminated)。調用"GetStringLength"來發現"16-bit Unicode"字符個數在字符串中。

  • 違反訪問控制規則:

"JNI"不能強制"c lass, field, and method"訪問控制限制,限制就是在"Java"編程語言層通過修飾符 的使用例如"private"和 "f inal"來表示的 。寫本地代碼訪問或修改一個對象的成員域是可能的,即使在"Java"編程語言層如此做將導致一個"IllagalAccessException"。

  • 漠視國際化:
  • 保留虛擬器資源:
  • 在本地方法中一個通常的錯誤是忘記釋放虛擬器的資源。

  • 過度的局部引用創建:

過度局部引用的創建導致程序不必要的保留內存。一個不必要的局部引用浪費爲被引用對象的
內存和爲引用自己的內存。

  • 使用無效的局部引用:
  • 局部引用只在一個本地方法的單次調用(invocation)中是有效的。當實現方法的本地函數返回後, 在一個本地方法調用中創建的局部引用自動被釋放。

  • 在線程間使用"JNIEnv":
  • "JNIEnv"指針,作爲第一個參數傳遞給每一個本地方法,只能在和它關聯的線程中被使用。從 一個線程得到緩衝的"JNIEnv"接口指針,同時在另一個線程中使用這個指針,是錯誤的。

  • 不匹配的線程模式:
  • 只有主機的本地代碼和"Java"虛擬器實現共享一樣線程的模式,JNI才能運行。

  •  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章