程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> JNI學習之Invocation API

JNI學習之Invocation API

編輯:C++入門知識

 

簡介

通過使用Invocation API,使用C/C++開發的本地應用可以訪問Java虛擬機提供的特性。為了描述簡單,下面提到的VM指的都是Java虛擬機。

創建VM

在本地應用裡,調用JNI_CreateJavaVM()方法可以完成初始化、加載VM,並返回指向新VM對象的一個指針。調用JNI_CreateJavaVM方法的線程,被稱為主線程。

線程與VM的關聯操作

JNIEnv對象並不是線程安全的,因此只能在當前線程使用。當需要跨線程使用JNIEnv對象時,需要通過調用AttachCurrentThread方法將當前線程與JVM進行關聯,並得到一個指向JNIEnv對象的指針。當AttachCurrentThread方法調用成功之後,當前本地線程即可被VM感知。由於本地線程並不由JVM創建,因而需要確保自身有足夠的棧空間來執行必要的代碼。

調用者可以在本地線程中調用DetachCurrentThread來解除關聯關系,以便於釋放資源,否則會導致資源洩漏;但在本地線程的調用棧內仍有Java方法時,調用DetachCurrentThread方法可能會失敗。

卸載VM

當VM使用完畢,就應當考慮停止VM並回收資源,通過調用JNI_DestroyJavaVM方法即可達到這一目的。在VM看來,用戶線程包括VM在執行Java字節碼時創建的Java線程,以及通過調用AttachCurrentThread方法進而與VM完成關聯的本地線程。用戶線程的代碼在執行時,可能會持有比如鎖、窗口之類的系統資源,為了簡化VM的實現,VM把釋放資源的操作留給程序員去做,VM要求調用JNI_DestroyJavaVM方法的當前線程必須是當前唯一存活的用戶線程,否則JNI_DestroyJavaVM方法調用後可能無法達到預期的效果。

動態庫和版本管理

本地動態庫被VM加載之後,對於VM內部所有的類加載器都是可見的。即VM內部由不同類加載器加載的兩個類可以關聯到相同的本地方法,這帶來兩個問題:

 

一個Java類可能會與由另外一個類加載觸發加載的本地庫建立關聯關系;本地方法無法區分當前的調用是來自VM內部由不同類加載器加載的哪個類,這破壞了由類加載器控制的類命名空間,從而可能引入類型安全相關的問題;

 

為了解決上述的兩個問題,引入了新的解決方法,即某個類加載器自己管理當前加載的本地庫的集合,並且相同的本地庫只能被一個類加載器加載。應用的代碼違反這兩點約束將導致UnsatisfiedLinkError的出現。

新方法的優點有:

 

基於類加載器實現的命名空間管理在本地庫的使用方面得到了保留,本地庫可以無需考慮來自不同類加載器的類的調用;當加載某個本地庫的類加載器被GC掉之後,本地庫也可以自動被釋放掉資源。

 

JNI_OnLoad

為了便於實現上述特性,VM暴露了方法JNI_OnLoad。在VM加載本地庫時,VM會自動在本地庫文件中查找這個方法,如果方法存在則通過這個方法來獲取本地庫使用的JNI版本號,這樣VM可以決定本地庫使用VM特性的請求是否合理。如果JNI_OnLoad方法沒有實現,VM認為本地庫基於JNI_VERSION_1_1相關的特性實現。如果VM無法識別JNI_OnLoad方法的返回值,VM會忽略本地庫的加載請求,並清理現場。

JNI_OnUnLoad

當加載本地庫的類加載器被GC之後,VM會主動調用本地庫導出的JNI_OnUnLoad方法,如果本地庫沒定義這個方法的話,這個步驟將自動忽略。一般而言,可以在JNI_OnUnLoad方法內部做一些清理操作。由於JNI_OnUnLoad方法被VM回調的時機不確定,因而要避免在這個方法內部調用Java語言的方法以及VM提供的特性。

Invocation API簡介

這一章節提到的API均由VM提供。方法的返回值為JNI_OK 時表示調用成功,非JNI_OK 表示調用失敗。

如下是常用的幾個結構定義。

 

typedef struct JavaVMInitArgs {
    jint version;
    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;

typedef struct JavaVMOption {
    char *optionString;  /* the option as a string in the default platform encoding */
    void *extraInfo;
} JavaVMOption;

typedef struct JavaVMAttachArgs {
    jint version;
    char *name;    /* the name of the thread as a modified UTF-8 string, or NULL */
    jobject group; /* global ref of a ThreadGroup object, or NULL */
} JavaVMAttachArgs;


 

JNI_GetDefaultJavaVMInitArgs

簽名為jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
通過調用本方法,可以得到VM的默認配置屬性。方法入參為指向JavaVMInitArgs類型對象的指針,在本地代碼調用JNI_GetDefaultJavaVMInitArgs方法前需要設置期望VM支持的版本號。方法調用返回值為JNI_OK時表示成功,VM會將版本號設置為實際支持的值;方法的返回值非JNI_OK時,表示調用失敗。

 

 

JNI_GetCreatedJavaVMs

簽名為jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
通過調用本方法,可以獲取到當前已創建的全部VM對象。vmBuf為保存指針的數組,長度由bufLen給出,而實際的VM數量將在nVMs變量中返回。
單個進程中不允許創建多個VM實例。

 

JNI_CreateJavaVM

簽名為jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);

創建VM對象,並完成必要的初始化操作,同時調用方法的線程被設置為主線程。在單個進程中不允許創建多個VM對象。

 

DestroyJavaVM

簽名為jint DestroyJavaVM(JavaVM *vm);

 

通過調用本方法,可以卸載已創建的VM對象,並回收相關的資源。本方法是線程安全的,可以在任意線程使用。本方法在使用時會阻塞當前線程嗎?假如調用線程沒有與VM對象建立了關聯關系,則直接建立關聯關系,然後等待其它用戶線程退出;假如已建立了關聯關系,則直接等待其它用戶線程退出。
Unloading of the VM is not supported.這語沒有看明白什麼意思。

AttachCurrentThread

簽名jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

當前調用線程與VM建立關聯關系,獲取到可在當前線程安全使用的JNIEnv對象。假如當前線程已經與VM建立關聯,多次調用本方法是安全的。本地線程在同一時段內,只能與一個VM對象建立關聯關系(這個說法很奇怪,之前的資源提示在單個進程內,只允許創建一個VM,這裡為什麼又提示說避免與多個VM關聯?)。
當本地線程與VM建立關聯之後,線程使用的上下文類加載器將是VM的啟動類加載器。

 

調用本方法時,第一個參數為指向VM對象的指針,第二個參數為JNIEnv類型對象的指針,第三個參數為JavaVMAttachArgs類型對象的指針,但沒有實際用途,應當為設置為NULL(不過原文檔對這個參數的介紹稍有點混亂,需要實際驗證一下)。

 

AttachCurrentThreadAsDaemon

簽名jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);

 

用法和參數與AttachCurrentThread方法類似,區別在於VM內部創建的java.lang.Thread對象將會是一個daemon。如果當前本地線程已經和VM建立關聯,則多次調用AttachCurrentThread或者AttachCurrentThreadAsDaemon並不會修改java.lang.Thread對象的daemon屬性。

 

DetachCurrentThread

簽名jint DetachCurrentThread(JavaVM *vm);

 

當前線程與VM解除關聯,本地線程持有的鎖對象將全部釋放。等待當前線程的Java線程將得到通知。主線程通過調用本方法,可以和VM解除關聯。
 

GetEnv

簽名為jint GetEnv(JavaVM *vm, void **env, jint version);

 

通過調用本方法,可以獲取到當前線程的JNIEnv對象。如果當前線程與VM沒有建立關聯,則*env被設置為NULL,同時返回JNI_EDETACHED;如果傳入的VM特性版本號不被支持,則*env被設置為NULL,同時返回JNI_EVERSION;如果調用成功,則*env被設置為正確的JNIEnv對象指針,同時返回JNI_OK。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved