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

目錄

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

第四章 JNI函數(shù)

4.15 操作監(jiān)視器(同步鎖)

MonitorEnter

jint MonitorEnter(JNIEnv *env, jobject obj);

進(jìn)入一個obj的監(jiān)視區(qū)monitor。

即使用 obj 作為鎖對象(可能是對象鎖,也可能是類鎖),obj必須不能為 null。

每個java對象內(nèi)部都有一個鎖(monitor),如果當(dāng)前線程已經(jīng)擁有了obj的monitor,它會在其監(jiān)視區(qū)增加計數(shù)器,記錄線程進(jìn)入監(jiān)視區(qū)的次數(shù)。如果obj上的監(jiān)視區(qū)沒有被任何其他線程持有,則當(dāng)前線程開始持有監(jiān)視區(qū)的鎖,設(shè)置監(jiān)視區(qū)的計數(shù)器為1;如果其他線程已經(jīng)擁有了這個監(jiān)視區(qū)的鎖,則當(dāng)前線程一直等待鎖被釋放,然后再嘗試獲取鎖。

通過 MonitorEnter 這個JNI函數(shù)進(jìn)入到一個監(jiān)視區(qū)中,是無法使用Java虛擬機(jī) monitorexit 指令或一個同步方法返回來退出這個監(jiān)視區(qū)的。MonitorEnter函數(shù)和Java虛擬機(jī) monitorexit 指令之間可能搶著進(jìn)入同一個obj的監(jiān)視區(qū)。

為了避免死鎖,使用 MonitorEnter方法來進(jìn)入監(jiān)視區(qū),必須使用MonitorExit 來退出,或調(diào)用 DetachCurrentThread 來明確釋放JNI監(jiān)視區(qū)。

參數(shù):

  • env :JNI接口指針
  • obj:一個普通的java對象或class對象。

返回值:

成功返回0,失敗返回負(fù)數(shù)。

使用實例:

env->MonitorEnter(mMonitor);
env->CallVoidMethod(mMonitor, mWaitMethod);
env->MonitorExit(mMonitor);

以上代碼來至:https://github.com/abhijit86k/iceweasel/blob/140d9f57f5e4240bec0545b5a650f8ef9d3f45d3/plugin/oji/MRJ/plugin/Source/MRJMonitor.cpp

?

MonitorExit

jint MonitorExit(JNIEnv *env, jobject obj);

當(dāng)前線程必須持有obj的monitor,當(dāng)前線程進(jìn)入monitor的計數(shù)器減1,如果計數(shù)器的值變?yōu)榱?,則當(dāng)前線程釋放monitor。

本地代碼不能使用 MonitorExit 函數(shù)來退出一個通過Java虛擬機(jī) monitorexit指令或一個同步方法進(jìn)入的monitor。

參數(shù):

  • env :JNI接口指針
  • obj:一個java對象或一個class對象

返回值:

成功返回0,失敗返回負(fù)數(shù)。

排除異常:

  • IllegalMonitorStateException如果當(dāng)前線程還沒持有monitor。

?

?

4.16 NIO支持

NewDirectByteBuffer

jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);

分配并返回一個 java.nio.ByteBuffer ,指向一塊內(nèi)存,地址開始于 address ,并有 capacity 個bytes的內(nèi)存空間。

本地代碼調(diào)用這個函數(shù)并生成byte-buffer對象給一個Java層,必須確保這個buffer指向一個有效的可讀內(nèi)存空間,如果需要,也得是可寫的內(nèi)存。如果從Java代碼嘗試訪問一個無效的內(nèi)存空間,可能返回一個隨機(jī)的值,或沒有任何可見反應(yīng),或拋出一個未指定的異常。

JDK/JRE 1.4及以上支持該函數(shù)。

參數(shù):

  • env :JNI接口指針
  • address:內(nèi)存空間起始地址(必須不為 NULL )
  • capacity:內(nèi)存空間的大小(字節(jié)數(shù))(必須為整數(shù))

返回值:

返回剛創(chuàng)建的 java.nio.ByteBuffer 對象的局部引用。如果有異常拋出或者虛擬機(jī)不允許JNI訪問直接的buffer,則返回 NULL

拋出異常:

  • OutOfMemoryError: 如果分配 ByteBuffer 對象失敗時。

使用實例:

  jobject pDataBuf = null;
  if (filep->data[0].len > 0)
    pDataBuf = env->NewDirectByteBuffer(filep->data[0].ptr,
                                        filep->data[0].len);
  env->SetObjectArrayElement(pParts, pidx++, pDataBuf);
  pDataBuf = null;
  if (filep->data[1].len > 0)
    pDataBuf = env->NewDirectByteBuffer(filep->data[1].ptr,
                                        filep->data[1].len);
  env->SetObjectArrayElement(pParts, pidx++, pDataBuf);

以上代碼來至:Jni.cpp (openjdk\jdk\src\share\native\com\sun\java\util\jar\pack)

?

GetDirectBufferAddress

void* GetDirectBufferAddress(JNIEnv* env, jobject buf);

獲取給定的 java.nio.Buffer 的起始內(nèi)存地址。

這個函數(shù)通過buffer對象允許本地代碼訪問Java代碼可訪問的同一塊內(nèi)存地址。

JDK/JRE 1.4及以上支持該函數(shù)。

參數(shù):

  • env :JNI接口指針
  • buf:給定的 java.nio.Buffer 對象(必須不能為 NULL )

返回值:

返回buffer指向的起始內(nèi)存地址。如果內(nèi)存地址是undefined的,如果給定的jobject不是 java.nio.Buffer 或者如果虛擬機(jī)不支持JNI訪問直接的buffer,則返回 NULL

使用實例:

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass2(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jobject data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{
    jbyte *body;
    char *utfName;
    jclass result = 0;
    char buf[128];
    char* utfSource;
    char sourceBuf[1024];

    assert(data != NULL); // caller fails if data is null.
    assert(length >= 0);  // caller passes ByteBuffer.remaining() for length, so never neg.
    // caller passes ByteBuffer.position() for offset, and capacity() >= position() + remaining()
    assert((*env)->GetDirectBufferCapacity(env, data) >= (offset + length));
  
    body = (*env)->GetDirectBufferAddress(env, data);

    if (body == 0) {
        JNU_ThrowNullPointerException(env, 0);
        return 0;
    }
  
    // ...
}

以上代碼來至:ClassLoader.c (openjdk\jdk\src\share\native\java\lang)

?

GetDirectBufferCapacity

jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);

獲取并返回給定 java.nio.Buffer 對象的內(nèi)存空間大?。╟apacity)。capacity是內(nèi)存空間包含的元素的個數(shù)(the number of elements that the memory region contains)

參數(shù):

  • env :JNI接口指針
  • buf : 給定的buffer對象。(必須不能為 NULL )

返回值:

返回buffer對應(yīng)的內(nèi)存空間的capacity。如果給定的jobject不是 java.nio.Buffer ,或者the object is an unaligned view buffer and the processor architecture does not support unaligned access,或者虛擬機(jī)不允許JNI訪問direct buffer,則返回 -1 。

使用實例:

請參見上一個函數(shù)的例子。

?

?

4.17 反射支持

開發(fā)者如果知道方法或域的名稱和類型,可以使用JNI來調(diào)用Java方法或訪問Java域。Java反射API提供運(yùn)行時自省。JNI提供一組函數(shù)來使用Java反射API。

?

FromReflectedMethod

jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

通過 java.lang.reflect.Methodjava.lang.reflect.Constructor 來獲取其反射的目標(biāo)方法對應(yīng)的 methodId.

參數(shù):

  • env :JNI接口指針
  • jobject:一個 java.lang.reflect.Methodjava.lang.reflect.Constructor 對象。

返回值:

反射的目標(biāo)方法的methodID

使用實例:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
        JNIEnv* env, jobject src, jobject dest) {
    jobject clazz = env->CallObjectMethod(dest, jClassMethod);
    ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
            dvmThreadSelf_fnPtr(), clazz);
    clz->status = CLASS_INITIALIZED;

    Method* meth = (Method*) env->FromReflectedMethod(src);
    Method* target = (Method*) env->FromReflectedMethod(dest);
    LOGD("dalvikMethod: %s", meth->name);

//  meth->clazz = target->clazz;
    meth->accessFlags |= ACC_PUBLIC;
    meth->methodIndex = target->methodIndex;
    meth->jniArgInfo = target->jniArgInfo;
    meth->registersSize = target->registersSize;
    meth->outsSize = target->outsSize;
    meth->insSize = target->insSize;

    meth->prototype = target->prototype;
    meth->insns = target->insns;
    meth->nativeFunc = target->nativeFunc;
}

以上代碼來至:https://github.com/alibaba/AndFix/blob/67a5e3c2c308569f39400cc3258545ee25538719/jni/dalvik/dalvik_method_replace.cpp

以下是個人理解部分:

這里講一個題外話,上面的例子是來至阿里巴巴開源的Android熱修復(fù)框架andFix。其中就使用了 FromReflectedMethod 來實現(xiàn)替換有BUG的方法,以實現(xiàn)熱修復(fù)的目標(biāo)。

它的實現(xiàn)原理是通過將目標(biāo)方法修改為native方法,然后修改其 nativeFunc 的指針來實現(xiàn)替換一個方法。

?

FromReflectedField

jfieldID FromReflectedField(JNIEnv *env, jobject field);

通過 java.lang.reflect.Field 對象來獲取fieldID.

參數(shù):

  • env :JNI接口指針
  • fieldjava.lang.reflect.Field 對象

返回值:

目標(biāo)成員域的fieldID

使用實例:

JNIEXPORT void JNICALL Java_brian_com_nativehotfixdemo_hotfix_HotfixManager_setSingleFlagArt
        (JNIEnv* env, jobject obj, jobject field) {
    Field* dalvikField = (Field*) env->FromReflectedField(field);
    dalvikField->accessFlags = dalvikField->accessFlags & (~ACC_PRIVATE) | ACC_PUBLIC;
}

以上代碼來至:https://github.com/brianxcli/NativeHotFixDemo/blob/b9e6399ca7ab18608bab69df62328cfa9223f39f/app/src/main/cpp/hotfix_dalvik.cpp

?

ToReflectedMethod

jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);

轉(zhuǎn)換 cls 的 methodID 為 java.lang.reflect.Methodjava.lang.reflect.Constructor對象。

參數(shù):

  • env :JNI接口指針
  • cls:目標(biāo)方法的java類對象
  • jmethodId :目標(biāo)方法的methodID
  • isStatic:目標(biāo)方法為靜態(tài)的則比較將 isStatic 設(shè)置為 JNI_TRUE ,否則設(shè)置為 JNI_FALSE 。

返回值:

java.lang.reflect.Methodjava.lang.reflect.Constructor對象

使用實例:

extern "C" JNIEXPORT jobject JNICALL Java_JniTest_testGetMirandaMethodNative(JNIEnv* env, jclass) {
  jclass abstract_class = env->FindClass("JniTest$testGetMirandaMethod_MirandaAbstract");
  assert(abstract_class != NULL);
  jmethodID miranda_method = env->GetMethodID(abstract_class, "inInterface", "()Z");
  assert(miranda_method != NULL);
  return env->ToReflectedMethod(abstract_class, miranda_method, JNI_FALSE);
}

以上代碼來至:https://github.com/android-security/android_art/blob/479fe13d929012cd45c7a92ce140dae0b71e026f/test/JniTest/jni_test.cc

?

ToReflectedField

jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);

轉(zhuǎn)換 cls 的 fieldID 為 java.lang.reflect.Field 對象。

參數(shù):

  • env :JNI接口指針
  • cls:目標(biāo)方法的java類對象
  • jmethodId :目標(biāo)方法的methodID
  • isStatic:目標(biāo)方法為靜態(tài)的則比較將 isStatic 設(shè)置為 JNI_TRUE ,否則設(shè)置為 JNI_FALSE 。

返回值:

java.lang.reflect.Field 對象

?

?

4.18 Java虛擬機(jī)接口

GetJavaVM

jint GetJavaVM(JNIEnv *env, JavaVM **vm);

返回關(guān)聯(lián)到當(dāng)前線程的Java虛擬機(jī)接口指針。

參數(shù):

  • env :JNI接口指針
  • vm: 用于存放返回的Java虛擬機(jī)指針

返回值:

成功返回0,失敗返回負(fù)數(shù)。

使用實例:

if (JNI_OnLoad) {
  JavaVM *jvm;
  (*env)->GetJavaVM(env, &jvm);
  jniVersion = (*JNI_OnLoad)(jvm, NULL);
} else {
  jniVersion = 0x00010001;
}

以上代碼來至:OpenJDK/jdk/src/share/native/java/lang/ClassLoader.c

?

?第五章 Invocation API

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

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

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