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

目录

第一章 介绍
第二章 设计机制
第三章 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容