JNI技術(shù)規(guī)范 - 第四章 JNI函數(shù)(1)

目錄

第一章 介紹
第二章 設(shè)計(jì)機(jī)制
第三章 JNI類型和數(shù)據(jù)結(jié)構(gòu)
第四章 JNI函數(shù)(1)
第四章 JNI函數(shù)(2)
第四章 JNI函數(shù)(3)
第四章 JNI函數(shù)(4)
第五章 Invocation API

第四章 JNI函數(shù)

本章作為JNI函數(shù)的參考手冊,提供了完整的JNI函數(shù)列表,及每個(gè)函數(shù)的文檔說明。

JNI開發(fā)者一定要注意標(biāo)記為must的限制。例如,當(dāng)你看到一個(gè)JNI函數(shù)表示 must 接受一個(gè)不為null的對象,name你就有義務(wù)一定不要傳null給該JNI函數(shù),因?yàn)镴NI函數(shù)在內(nèi)部并不會(huì)檢查空指針,你需要在調(diào)用之前自己檢查。

這一章節(jié)的部分內(nèi)容和網(wǎng)景提出的JRI文檔是兼容的。

?

注意:因?yàn)楣俜轿臋n中對于每個(gè)函數(shù)都沒有舉出例子說明,為了便于理解,因此本文作者會(huì)盡量對每個(gè)函數(shù)找出使用的實(shí)例來便于理解。請注意這些實(shí)例并非官方文檔的一部分。

?

4.1 接口函數(shù)表

所有的JNI函數(shù)都是通過傳遞進(jìn)來固定的第一個(gè)參數(shù) JNIEnv 參數(shù)來進(jìn)行訪問到的。JNIEnv 類型是一個(gè)指向所有JNI函數(shù)指針集合的指針。它的定義如下:

typedef const struct JNINativeInteface *JNIEnv;

虛擬機(jī)初始化函數(shù)表如下,前三個(gè)reserved值是為了兼容COM標(biāo)準(zhǔn):

const struct JNINativeInterface_ {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    // 后面的省略掉,函數(shù)太多了。
};

?

4.2 版本信息

GetVersion

jint GetVersion(JNIEnv *env);

返回JNI的版本號。

參數(shù):

  • env: jni接口指針

返回值:

返回一個(gè)值,其中高位為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

使用實(shí)例:

jint version = env->GetVersion();

?

4.3 操作類

DefineClass

jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
                  const jbyte *buf, jsize bufLen);

題外話:請注意這個(gè)函數(shù)不會(huì)Android支持,因?yàn)锳ndroid并不使用Java字節(jié)碼或class文件,所以傳入二進(jìn)制的class data不會(huì)起作用。

從包含二進(jìn)制的類數(shù)據(jù)(binary class data)的buffer中載入類。

在調(diào)用DefineClass完畢后,虛擬機(jī)不會(huì)再引用這個(gè)buffer,你可以釋放掉它。

參數(shù):

  • env:JNI接口指針
  • name:需要加載的類或接口的短名字,這個(gè)字符串使用MUTF-8編碼。
  • loader:指派用來加載類的ClassLoader
  • buf:包含 .class 文件數(shù)據(jù)的 buffer
  • bufLen: buffer的長度

返回值:

返回加載的Java類對象(java class object),或者如果出現(xiàn)錯(cuò)誤則返回NULL

拋出異常:

ClassFormatError :如果傳入的class內(nèi)容不是一個(gè)有效的class文件。

ClassCircularityError:如果class或interface是它自己的父類或父接口,造成循環(huán)層級關(guān)系。

OutOfMemoryError:如果系統(tǒng)在載入的過程中內(nèi)存不足。

SecurityException :如果調(diào)用者啟動(dòng)載入java包內(nèi)置的類,引發(fā)安全隱患。

使用實(shí)例:

// 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);

以上代碼來至:https://github.com/poidasmith/winrun4j/blob/e4ab3564c7a580a9d4801c5954aacda667f88b3b/WinRun4J/src/java/JNI.cpp

?

FindClass

jclass FindClass(JNIEnv *env, const char *name);

在JDK 1.1發(fā)行版中,這個(gè)函數(shù)加載本地定義的類(locally-defined class), 它搜索在由環(huán)境變量 CLASSPATH 目錄下的子目錄和zip文件中搜索指定的類名。

從JDK 1.2發(fā)行版之后,Java安全模型允許非本地系統(tǒng)類也可以加載和調(diào)用本地方法。FindClass 函數(shù)會(huì)使用與當(dāng)前本地方法關(guān)聯(lián)的ClassLoader, 并用它來加載本地方法指定的class。如果本地方法屬于系統(tǒng)類(system class),則沒有ClassLoader會(huì)被調(diào)用。否則,將使用正確的ClassLoader來加載(load)和鏈接(link)指定名稱的類。

從JDK 1.2發(fā)行版之后,當(dāng)通過 Invocation 接口來調(diào)用 FindClass 函數(shù),將沒有當(dāng)前的本地方法或與之關(guān)聯(lián)的ClassLoader。這種情況下,會(huì)使用 ClassLoader.getSystemClassLoader 來替代。這個(gè)ClassLoader 是虛擬機(jī)用來創(chuàng)建應(yīng)用(applications)的,它有能力定位到 java.class.path 參數(shù)下的所有類。

第二個(gè) name 參數(shù),使用全稱類名或數(shù)組類型簽名(array type signature)。例如,String類的全稱類名為:

"java/lang/String"

而Object數(shù)組類型的使用:

"[Ljava/lang/Object;"

題外話:一定要注意分隔符不是 . 而是 / ,不要寫錯(cuò)了。

參數(shù):

  • env:JNI接口指針
  • name:全稱的類名(包名以 / 作為分隔符, 然后緊跟著類名),如果名字以 [開頭(數(shù)組簽名標(biāo)識符),則返回一個(gè)數(shù)組的類,這個(gè)字符串也是MUTF-8。

返回值:

指定名稱的類的對象(a class object),或者在沒有找到對應(yīng)類時(shí)返回 NULL

拋出異常:

ClassFormatError :如果class內(nèi)容不是一個(gè)有效的class文件。

ClassCircularityError:如果class或interface是它自己的父類或父接口,造成循環(huán)層級關(guān)系。

OutOfMemoryError:如果系統(tǒng)在載入的過程中內(nèi)存不足。

NoClassDefFoundError:如果指定的類或接口沒有被找到。(當(dāng)name傳null或超長時(shí)也會(huì)拋出這個(gè)異常)

題外話:關(guān)于Java安全模型更多信息可參考:https://www.ibm.com/developerworks/cn/java/j-lo-javasecurity/index.html

使用實(shí)例:

 // 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 參數(shù)不是 java/lang/Object 則返回該類的父類。

如果傳入的 clazz 參數(shù)是 java/lang/Object 則返回NULL,因?yàn)樗鼪]有父類。當(dāng)傳入的是一個(gè)接口,而不是類時(shí),也返回 NULL 。

參數(shù):

  • env:JNI接口指針
  • clazz: Java類對象(java class object)

返回值:

返回傳入的 clazz 的父類,或 NULL .

使用實(shí)例:

jclass clazz = env->GetObjectClass(thiz);

clazz = env->GetSuperclass(clazz);

jfieldID __state = env->GetFieldID(clazz, "__state", "J");

以上代碼來至:https://github.com/liucheng98/mesos-0.22.0/blob/5cfd2c36c0e8361fa2e2d9b6f191a738d22a9cfd/src/java/jni/org_apache_mesos_state_LogState.cpp

?

IsAssignableForm

jboolean IsAssignableFrom(JNIEnv *env, jclass class1, jclass clazz2);

檢查 clazz1 的對象是否能被安全的轉(zhuǎn)型(cast)為 clazz2

參數(shù):

  • env:JNI接口指針
  • clazz1:第一個(gè)class參數(shù)(需要轉(zhuǎn)型的類)
  • clazz2:第二個(gè)class參數(shù)(轉(zhuǎn)型的目標(biāo)類)

返回值:

如果是以下情況則返回 JNI_TRUE :

  • clazz1 和 clazz2 指向同一個(gè)java類
  • clazz1 是 clazz2 的子類。(向上轉(zhuǎn)型是安全的)
  • clazz1 是 clazz2(接口)的實(shí)現(xiàn)類。(也屬于向上轉(zhuǎn)型)

使用實(shí)例:

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);

觸發(fā)一個(gè) java.lang.Throwable 對象的異常被拋出。

參數(shù):

  • env:JNI接口指針
  • objjava.lang.Throwable 對象

返回值:

成功則返回0, 失敗時(shí)返回賦值

拋出異常:

拋出 java.lang.Throwable 對象

使用實(shí)例:

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對象的構(gòu)造器函數(shù),message為異常的錯(cuò)誤消息,clazz為異常的類。

參數(shù):

  • env:JNI接口指針
  • clazzjava.lang.Throwable 的子類
  • message: 用于創(chuàng)建 java.lang.Throwable 對象時(shí)傳入的錯(cuò)誤消息。這個(gè)是字符串是MUTF-8編碼。

返回值:

成功則返回0, 失敗時(shí)返回賦值

拋出異常:

拋出剛構(gòu)造出來的 java.lang.Throwable 對象

使用實(shí)例:

env->ThrowNew(env->FindClass("sun/jvm/hotspot/debugger/DebuggerException"), errMsg);

?

ExceptionOccurred

jthrowable ExceptionOccurred(JNIEnv *env);

檢查是否有異常被拋出。這個(gè)異常在本地方法調(diào)用 ExceptionClear() 方法或被Java代碼處理這個(gè)異常之前都會(huì)保持在被拋出狀態(tài)。

參數(shù):

  • env:JNI接口指針

返回值:

返回過程中拋出的異常,或沒有異常被拋出時(shí)返回 NULL 。

使用實(shí)例:

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);

打印一個(gè)異常的stack trace到系統(tǒng)的錯(cuò)誤輸出,例如 stderr 這是為了調(diào)試提供便利。

參數(shù):

  • env:JNI接口指針

使用實(shí)例:

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);

清理任何即將拋出的異常。如果沒有異常被拋出,則不起任何作用。

參數(shù):

  • env:JNI接口指針

使用實(shí)例:

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);

拋出一個(gè)嚴(yán)重錯(cuò)誤,并不希望虛擬機(jī)恢復(fù)。

參數(shù):

  • env:JNI接口指針
  • msg : 錯(cuò)誤消息,這個(gè)字符串為MUTF-8編碼

使用實(shí)例:

env->CallVoidMethod(currentThread, setThreadName, threadName) ;
if( env->ExceptionCheck() ) {
  env->ExceptionDescribe() ;
  env->FatalError("setting thread name failed: could not start reading thread");
  return 0L ;
}

以上代碼來至:https://github.com/tnarnold/netsnmpj/blob/3153c3e9297dd7de7db9d1a65c97f77937ae147b/netsnmpj-preliminary/native/nativeThread.cc

?

ExceptionCheck

jboolean ExceptionCheck(JNIEnv *env);

這是一個(gè)快速函數(shù)用于檢查是否有被拋出的異常,而不創(chuàng)建一個(gè)這個(gè)異常的局部引用。

參數(shù):

  • env:JNI接口指針

返回值:

有一個(gè)即將被拋出的異常時(shí)返回 JNI_TURE ,沒有則返回 JNI_FALSE

使用實(shí)例:

env->CallVoidMethod(thread, runId);

if (env->ExceptionCheck()) {
  env->ExceptionDescribe();
  env->ExceptionClear();
  // handle exception
}

以上代碼來至:OpenJDK 源碼中的 /jdk/src/windows/native/sun/windows/awt_Toolkit.cpp

?

第四章 JNI函數(shù)(2)

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容