美文网首页Android知识
JNI技术规范 - 第四章 JNI函数(4)

JNI技术规范 - 第四章 JNI函数(4)

作者: 骆驼骑士 | 来源:发表于2017-04-08 17:38 被阅读607次

    目录

    第一章 介绍
    第二章 设计机制
    第三章 JNI类型和数据结构
    第四章 JNI函数(1)
    第四章 JNI函数(2)
    第四章 JNI函数(3)
    第四章 JNI函数(4)
    第五章 Invocation API

    第四章 JNI函数

    4.15 操作监视器(同步锁)

    MonitorEnter

    jint MonitorEnter(JNIEnv *env, jobject obj);
    

    进入一个obj的监视区monitor。

    即使用 obj 作为锁对象(可能是对象锁,也可能是类锁),obj必须不能为 null。

    每个java对象内部都有一个锁(monitor),如果当前线程已经拥有了obj的monitor,它会在其监视区增加计数器,记录线程进入监视区的次数。如果obj上的监视区没有被任何其他线程持有,则当前线程开始持有监视区的锁,设置监视区的计数器为1;如果其他线程已经拥有了这个监视区的锁,则当前线程一直等待锁被释放,然后再尝试获取锁。

    通过 MonitorEnter 这个JNI函数进入到一个监视区中,是无法使用Java虚拟机 monitorexit 指令或一个同步方法返回来退出这个监视区的。MonitorEnter函数和Java虚拟机 monitorexit 指令之间可能抢着进入同一个obj的监视区。

    为了避免死锁,使用 MonitorEnter方法来进入监视区,必须使用MonitorExit 来退出,或调用 DetachCurrentThread 来明确释放JNI监视区。

    参数:

    • env :JNI接口指针
    • obj:一个普通的java对象或class对象。

    返回值:

    成功返回0,失败返回负数。

    使用实例:

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

    当前线程必须持有obj的monitor,当前线程进入monitor的计数器减1,如果计数器的值变为了0,则当前线程释放monitor。

    本地代码不能使用 MonitorExit 函数来退出一个通过Java虚拟机 monitorexit指令或一个同步方法进入的monitor。

    参数:

    • env :JNI接口指针
    • obj:一个java对象或一个class对象

    返回值:

    成功返回0,失败返回负数。

    排除异常:

    • IllegalMonitorStateException如果当前线程还没持有monitor。

    4.16 NIO支持

    NewDirectByteBuffer

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

    分配并返回一个 java.nio.ByteBuffer ,指向一块内存,地址开始于 address ,并有 capacity 个bytes的内存空间。

    本地代码调用这个函数并生成byte-buffer对象给一个Java层,必须确保这个buffer指向一个有效的可读内存空间,如果需要,也得是可写的内存。如果从Java代码尝试访问一个无效的内存空间,可能返回一个随机的值,或没有任何可见反应,或抛出一个未指定的异常。

    JDK/JRE 1.4及以上支持该函数。

    参数:

    • env :JNI接口指针
    • address:内存空间起始地址(必须不为 NULL )
    • capacity:内存空间的大小(字节数)(必须为整数)

    返回值:

    返回刚创建的 java.nio.ByteBuffer 对象的局部引用。如果有异常抛出或者虚拟机不允许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 的起始内存地址。

    这个函数通过buffer对象允许本地代码访问Java代码可访问的同一块内存地址。

    JDK/JRE 1.4及以上支持该函数。

    参数:

    • env :JNI接口指针
    • buf:给定的 java.nio.Buffer 对象(必须不能为 NULL )

    返回值:

    返回buffer指向的起始内存地址。如果内存地址是undefined的,如果给定的jobject不是 java.nio.Buffer 或者如果虚拟机不支持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 对象的内存空间大小(capacity)。capacity是内存空间包含的元素的个数(the number of elements that the memory region contains)

    参数:

    • env :JNI接口指针
    • buf : 给定的buffer对象。(必须不能为 NULL )

    返回值:

    返回buffer对应的内存空间的capacity。如果给定的jobject不是 java.nio.Buffer ,或者the object is an unaligned view buffer and the processor architecture does not support unaligned access,或者虚拟机不允许JNI访问direct buffer,则返回 -1

    使用实例:

    请参见上一个函数的例子。

    4.17 反射支持

    开发者如果知道方法或域的名称和类型,可以使用JNI来调用Java方法或访问Java域。Java反射API提供运行时自省。JNI提供一组函数来使用Java反射API。

    FromReflectedMethod

    jmethodID FromReflectedMethod(JNIEnv *env, jobject method);
    

    通过 java.lang.reflect.Methodjava.lang.reflect.Constructor 来获取其反射的目标方法对应的 methodId.

    参数:

    • env :JNI接口指针
    • jobject:一个 java.lang.reflect.Methodjava.lang.reflect.Constructor 对象。

    返回值:

    反射的目标方法的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热修复框架andFix。其中就使用了 FromReflectedMethod 来实现替换有BUG的方法,以实现热修复的目标。

    它的实现原理是通过将目标方法修改为native方法,然后修改其 nativeFunc 的指针来实现替换一个方法。

    FromReflectedField

    jfieldID FromReflectedField(JNIEnv *env, jobject field);
    

    通过 java.lang.reflect.Field 对象来获取fieldID.

    参数:

    • env :JNI接口指针
    • fieldjava.lang.reflect.Field 对象

    返回值:

    目标成员域的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);
    

    转换 cls 的 methodID 为 java.lang.reflect.Methodjava.lang.reflect.Constructor对象。

    参数:

    • env :JNI接口指针
    • cls:目标方法的java类对象
    • jmethodId :目标方法的methodID
    • isStatic:目标方法为静态的则比较将 isStatic 设置为 JNI_TRUE ,否则设置为 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);
    

    转换 cls 的 fieldID 为 java.lang.reflect.Field 对象。

    参数:

    • env :JNI接口指针
    • cls:目标方法的java类对象
    • jmethodId :目标方法的methodID
    • isStatic:目标方法为静态的则比较将 isStatic 设置为 JNI_TRUE ,否则设置为 JNI_FALSE

    返回值:

    java.lang.reflect.Field 对象

    4.18 Java虚拟机接口

    GetJavaVM

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

    返回关联到当前线程的Java虚拟机接口指针。

    参数:

    • env :JNI接口指针
    • vm: 用于存放返回的Java虚拟机指针

    返回值:

    成功返回0,失败返回负数。

    使用实例:

    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

    相关文章

      网友评论

        本文标题:JNI技术规范 - 第四章 JNI函数(4)

        本文链接:https://www.haomeiwen.com/subject/ykzlattx.html