目錄
第一章 介紹
第二章 設計機制
第三章 JNI類型和數據結構
第四章 JNI函數(1)
第四章 JNI函數(2)
第四章 JNI函數(3)
第四章 JNI函數(4)
第五章 Invocation API
第四章 JNI函數
本章作為JNI函數的參考手冊,提供了完整的JNI函數列表,及每個函數的文檔說明。
JNI開發者一定要注意標記為must
的限制。例如,當你看到一個JNI函數表示 must
接受一個不為null的對象,name你就有義務一定不要傳null給該JNI函數,因為JNI函數在內部并不會檢查空指針,你需要在調用之前自己檢查。
這一章節的部分內容和網景提出的JRI文檔是兼容的。
?
注意:因為官方文檔中對于每個函數都沒有舉出例子說明,為了便于理解,因此本文作者會盡量對每個函數找出使用的實例來便于理解。請注意這些實例并非官方文檔的一部分。
?
4.1 接口函數表
所有的JNI函數都是通過傳遞進來固定的第一個參數 JNIEnv
參數來進行訪問到的。JNIEnv
類型是一個指向所有JNI函數指針集合的指針。它的定義如下:
typedef const struct JNINativeInteface *JNIEnv;
虛擬機初始化函數表如下,前三個reserved值是為了兼容COM標準:
const struct JNINativeInterface_ {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
FindClass,
FromReflectedMethod,
FromReflectedField,
ToReflectedMethod,
// 后面的省略掉,函數太多了。
};
?
4.2 版本信息
GetVersion
jint GetVersion(JNIEnv *env);
返回JNI的版本號。
參數:
-
env
: jni接口指針
返回值:
返回一個值,其中高位為major版本號返回,低位為minor版本號。
在 JDK/JRE 1.1中返回 0x00010001
在 JDK/JRE 1.2中返回 0x00010002
在 JDK/JRE 1.4中返回 0x00010004
在 JDK/JRE 1.6中返回 0x00010006
常量:
SINCE JDK/JRE 1.2:
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
/* Error codes */
#define JNI_EDETACHED (-2) /* thread detached from the VM */
#define JNI_EVERSION (-3) /* JNI version error
SINCE JDK/JRE 1.4:
#define JNI_VERSION_1_4 0x00010004
SINCE JDK/JRE 1.6:
#define JNI_VERSION_1_6 0x00010006
使用實例:
jint version = env->GetVersion();
?
4.3 操作類
DefineClass
jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
const jbyte *buf, jsize bufLen);
題外話:請注意這個函數不會Android支持,因為Android并不使用Java字節碼或class文件,所以傳入二進制的class data不會起作用。
從包含二進制的類數據(binary class data)的buffer中載入類。
在調用DefineClass完畢后,虛擬機不會再引用這個buffer,你可以釋放掉它。
參數:
-
env
:JNI接口指針 -
name
:需要加載的類或接口的短名字,這個字符串使用MUTF-8編碼。 -
loader
:指派用來加載類的ClassLoader -
buf
:包含 .class 文件數據的 buffer -
bufLen
: buffer的長度
返回值:
返回加載的Java類對象(java class object),或者如果出現錯誤則返回NULL
拋出異常:
ClassFormatError
:如果傳入的class內容不是一個有效的class文件。
ClassCircularityError
:如果class或interface是它自己的父類或父接口,造成循環層級關系。
OutOfMemoryError
:如果系統在載入的過程中內存不足。
SecurityException
:如果調用者啟動載入java包內置的類,引發安全隱患。
使用實例:
// Read in file from temp source
HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD cbBuffer = GetFileSize(hFile, 0);
PBYTE pBuffer = (PBYTE) malloc(cbBuffer);
ReadFile(hFile, pBuffer, cbBuffer, &cbBuffer, 0);
CloseHandle(hFile);
// DefineClass
jclass cl = env->DefineClass(name, loader, (const jbyte*) pBuffer, cbBuffer);
free(pBuffer);
?
FindClass
jclass FindClass(JNIEnv *env, const char *name);
在JDK 1.1發行版中,這個函數加載本地定義的類(locally-defined class), 它搜索在由環境變量 CLASSPATH
目錄下的子目錄和zip文件中搜索指定的類名。
從JDK 1.2發行版之后,Java安全模型允許非本地系統類也可以加載和調用本地方法。FindClass
函數會使用與當前本地方法關聯的ClassLoader, 并用它來加載本地方法指定的class。如果本地方法屬于系統類(system class),則沒有ClassLoader會被調用。否則,將使用正確的ClassLoader來加載(load)和鏈接(link)指定名稱的類。
從JDK 1.2發行版之后,當通過 Invocation 接口來調用 FindClass
函數,將沒有當前的本地方法或與之關聯的ClassLoader。這種情況下,會使用 ClassLoader.getSystemClassLoader
來替代。這個ClassLoader 是虛擬機用來創建應用(applications)的,它有能力定位到 java.class.path
參數下的所有類。
第二個 name
參數,使用全稱類名或數組類型簽名(array type signature)。例如,String類的全稱類名為:
"java/lang/String"
而Object數組類型的使用:
"[Ljava/lang/Object;"
題外話:一定要注意分隔符不是
.
而是/
,不要寫錯了。
參數:
-
env
:JNI接口指針 -
name
:全稱的類名(包名以/
作為分隔符, 然后緊跟著類名),如果名字以[
開頭(數組簽名標識符),則返回一個數組的類,這個字符串也是MUTF-8。
返回值:
指定名稱的類的對象(a class object),或者在沒有找到對應類時返回 NULL
拋出異常:
ClassFormatError
:如果class內容不是一個有效的class文件。
ClassCircularityError
:如果class或interface是它自己的父類或父接口,造成循環層級關系。
OutOfMemoryError
:如果系統在載入的過程中內存不足。
NoClassDefFoundError
:如果指定的類或接口沒有被找到。(當name傳null或超長時也會拋出這個異常)
題外話:關于Java安全模型更多信息可參考:https://www.ibm.com/developerworks/cn/java/j-lo-javasecurity/index.html
使用實例:
// Start thread which receives commands from the SA.
jclass threadClass = env->FindClass("java/lang/Thread");
if (threadClass == NULL) stop("Unable to find class java/lang/Thread");
jstring threadName = env->NewStringUTF("Serviceability Agent Command Thread");
if (threadName == NULL) stop("Unable to allocate debug thread name");
jmethodID ctor = env->GetMethodID(threadClass, "<init>", "(Ljava/lang/String;)V");
if (ctor == NULL) stop("Unable to find appropriate constructor for java/lang/Thread");
// Allocate thread object
jthread thr = (jthread) env->NewObject(threadClass, ctor, threadName);
以上代碼來之 openJDK 源碼 中的 /hotspot/agent/src/share/native/jvmdi/sa.cpp
?
GetSuperclass
jclass GetSuperclass(JNIEnv *env, jclass clazz);
只要傳入的 clazz
參數不是 java/lang/Object
則返回該類的父類。
如果傳入的 clazz
參數是 java/lang/Object
則返回NULL,因為它沒有父類。當傳入的是一個接口,而不是類時,也返回 NULL
。
參數:
-
env
:JNI接口指針 -
clazz
: Java類對象(java class object)
返回值:
返回傳入的 clazz
的父類,或 NULL
.
使用實例:
jclass clazz = env->GetObjectClass(thiz);
clazz = env->GetSuperclass(clazz);
jfieldID __state = env->GetFieldID(clazz, "__state", "J");
?
IsAssignableForm
jboolean IsAssignableFrom(JNIEnv *env, jclass class1, jclass clazz2);
檢查 clazz1
的對象是否能被安全的轉型(cast)為 clazz2
參數:
-
env
:JNI接口指針 -
clazz1
:第一個class參數(需要轉型的類) -
clazz2
:第二個class參數(轉型的目標類)
返回值:
如果是以下情況則返回 JNI_TRUE
:
- clazz1 和 clazz2 指向同一個java類
- clazz1 是 clazz2 的子類。(向上轉型是安全的)
- clazz1 是 clazz2(接口)的實現類。(也屬于向上轉型)
使用實例:
jclass listInterface = env->FindClass("java/util/List")
jclass arrayListClass = env->FindClass("java/util/ArrayList")
jboolean isSafe = env->IsAssignableFrom(arrayListClass, listInterface);
// isSafe: true;
isSafe = env->IsAssignableFrom(listInterface, arrayListClass);
// isSafe: false;
?
4.4 異常
Throw
jint Throw(JNIEnv *env, jthrowable obj);
觸發一個 java.lang.Throwable
對象的異常被拋出。
參數:
-
env
:JNI接口指針 -
obj
:java.lang.Throwable
對象
返回值:
成功則返回0, 失敗時返回賦值
拋出異常:
拋出 java.lang.Throwable
對象
使用實例:
jthrowable exception = env->ExceptionOccurred();
if (exception) {
env->ExceptionClear();
detach_internal(env, this_obj);
env->Throw(exception);
return;
}
以上代碼來至 OpenJDK 源碼 /hotspot/agent/src/os/solaris/proc/saproc.cpp
?
ThrowNew
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
Exception對象的構造器函數,message為異常的錯誤消息,clazz為異常的類。
參數:
-
env
:JNI接口指針 -
clazz
:java.lang.Throwable
的子類 -
message
: 用于創建java.lang.Throwable
對象時傳入的錯誤消息。這個是字符串是MUTF-8編碼。
返回值:
成功則返回0, 失敗時返回賦值
拋出異常:
拋出剛構造出來的 java.lang.Throwable
對象
使用實例:
env->ThrowNew(env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"), errMsg);
?
ExceptionOccurred
jthrowable ExceptionOccurred(JNIEnv *env);
檢查是否有異常被拋出。這個異常在本地方法調用 ExceptionClear()
方法或被Java代碼處理這個異常之前都會保持在被拋出狀態。
參數:
-
env
:JNI接口指針
返回值:
返回過程中拋出的異常,或沒有異常被拋出時返回 NULL
。
使用實例:
jthrowable exception = env->ExceptionOccurred();
if (exception) {
env->ExceptionClear();
detach_internal(env, this_obj);
env->Throw(exception);
return;
}
以上代碼來至 OpenJDK 源碼 /hotspot/agent/src/os/solaris/proc/saproc.cpp
?
ExceptionDescribe
void ExceptionDescribe(JNIEnv *env);
打印一個異常的stack trace到系統的錯誤輸出,例如 stderr
這是為了調試提供便利。
參數:
-
env
:JNI接口指針
使用實例:
jthrowable exc = safe_ExceptionOccurred(env);
if (exc) {
env->DeleteLocalRef(exc);
env->ExceptionDescribe();
env->ExceptionClear();
}
以上代碼來至:OpenJDK 源碼中的 /jdk/src/windows/native/sun/windows/awt_Object.cpp
?
ExceptionChar
void ExceptionClear(JNIEnv *env);
清理任何即將拋出的異常。如果沒有異常被拋出,則不起任何作用。
參數:
-
env
:JNI接口指針
使用實例:
jthrowable exc = safe_ExceptionOccurred(env);
if (exc) {
env->DeleteLocalRef(exc);
env->ExceptionDescribe();
env->ExceptionClear();
}
以上代碼來至:OpenJDK 源碼中的 /jdk/src/windows/native/sun/windows/awt_Object.cpp
?
FatalError
void FatalError(JNIEnv *env, const char *msg);
拋出一個嚴重錯誤,并不希望虛擬機恢復。
參數:
-
env
:JNI接口指針 -
msg
: 錯誤消息,這個字符串為MUTF-8編碼
使用實例:
env->CallVoidMethod(currentThread, setThreadName, threadName) ;
if( env->ExceptionCheck() ) {
env->ExceptionDescribe() ;
env->FatalError("setting thread name failed: could not start reading thread");
return 0L ;
}
?
ExceptionCheck
jboolean ExceptionCheck(JNIEnv *env);
這是一個快速函數用于檢查是否有被拋出的異常,而不創建一個這個異常的局部引用。
參數:
-
env
:JNI接口指針
返回值:
有一個即將被拋出的異常時返回 JNI_TURE
,沒有則返回 JNI_FALSE
使用實例:
env->CallVoidMethod(thread, runId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
// handle exception
}
以上代碼來至:OpenJDK 源碼中的 /jdk/src/windows/native/sun/windows/awt_Toolkit.cpp
?
?