JNI实现源码分析【四 函数调用】正文0x01:dvmCallMethodV0x02:nativeFunc0x03: 何时赋值

这是JNI实现源码分析系列文章中的一部分,本系列文章结合Dalvik源码来说明JNI实现上的细节,本系列包括:

JNI实现源码分析【一 前言】

JNI实现源码分析【二 数据结构】

JNI实现源码分析【三 间接引用表】

JNI实现源码分析【四 函数调用】

JNI实现源码分析【五 结束语】

正文

有了前面的铺垫,终于可以说说虚拟机是如何调用JNI方法的了。JNI方法,对应Java中的native方法,所以我们跟踪对Native方法的处理即可。

彻底弄懂dalvik字节码【一】中,我们跟踪过非Native方法的调用,现在我们来跟踪Native方法的调用,从dvmCallMethodV入手吧:

0x01:dvmCallMethodV

void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
    bool fromJni, JValue* pResult, va_list args)
{
    ...
    if (dvmIsNativeMethod(method)) {
        TRACE_METHOD_ENTER(self, method);
        /*
         * Because we leave no space for local variables, "curFrame" points
         * directly at the method arguments.
         */
        (*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
                              method, self);
        TRACE_METHOD_EXIT(self, method);
    } else {
        dvmInterpret(self, method, pResult);
    }
   ...
}

可以看到,当发现是Native方法时,直接调用Method.nativeFunc

0x02:nativeFunc

看看Method中的定义:

DalvikBridgeFunc nativeFunc;

typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult,
    const Method* method, struct Thread* self);

原来是个函数指针。名字既然叫做Bridge,说明是桥接的作用,即主要起到方法的串联和参数的适配作用。

0x03: 何时赋值

那么这个函数指针何时被赋值了呢? 有好几处。

a. dvmResolveNativeMethod 在Class.cpploadMethodFromDex中,我们看到:

        if (dvmIsNativeMethod(meth)) {
            meth->nativeFunc = dvmResolveNativeMethod;
            meth->jniArgInfo = computeJniArgInfo(&meth->prototype);
        }

loadMethodFromDex在从dex中加载类的时候会被调用,也就是说,这里是最初的调用。 所以就是场景就是:我们需要使用到Dex中的一个类,这个类第一次被加载,构建这个类的方法时,发现是一个native方法,将nativeFunc设置成为dvmResolveNativeMethod。

看看dvmResolveNativeMethod做了啥:

void dvmResolveNativeMethod(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    ClassObject* clazz = method->clazz;

    /*
     * If this is a static method, it could be called before the class
     * has been initialized.
     */
    if (dvmIsStaticMethod(method)) {
        if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
            assert(dvmCheckException(dvmThreadSelf()));
            return;
        }
    } else {
        assert(dvmIsClassInitialized(clazz) ||
               dvmIsClassInitializing(clazz));
    }

    /* start with our internal-native methods */
    DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
    if (infunc != NULL) {
        /* resolution always gets the same answer, so no race here */
        IF_LOGVV() {
            char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
            LOGVV("+++ resolved native %s.%s %s, invoking",
                clazz->descriptor, method->name, desc);
            free(desc);
        }
        if (dvmIsSynchronizedMethod(method)) {
            ALOGE("ERROR: internal-native can't be declared 'synchronized'");
            ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);
            dvmAbort();     // harsh, but this is VM-internal problem
        }
        DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
        dvmSetNativeFunc((Method*) method, dfunc, NULL);
        dfunc(args, pResult, method, self);
        return;
    }

    /* now scan any DLLs we have loaded for JNI signatures */
    void* func = lookupSharedLibMethod(method);
    if (func != NULL) {
        /* found it, point it at the JNI bridge and then call it */
        dvmUseJNIBridge((Method*) method, func);
        (*method->nativeFunc)(args, pResult, method, self);
        return;
    }

    IF_ALOGW() {
        char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
        ALOGW("No implementation found for native %s.%s:%s",
            clazz->descriptor, method->name, desc);
        free(desc);
    }

    dvmThrowUnsatisfiedLinkError("Native method not found", method);
}

其中dvmLookupInternalNativeMethod是查找这个方法是不是属于虚拟机里面定义的Native方法,如果是,则直接调用调用。我们自己写的native方法自然不是这里。

再看lookupSharedLibMethod,从classpath下的so中查找对应的函数,函数名称使用了如下格式:

alling dlsym(Java_com_sina_weibo_sdk_net_HttpManager_calcOauthSignNative)
calling dlsym(Java_com_sina_weibo_sdk_net_HttpManager_calcOauthSignNative__Landroid_content_Context_2Ljava_lang_String_2Ljava_lang_String_2)

函数名称构建的代码:

    ...
    mangleCM = mangleString(preMangleCM, len);
    if (mangleCM == NULL)
        goto bail;

    ALOGV("+++ calling dlsym(%s)", mangleCM);
    func = dlsym(pLib->handle, mangleCM);
    if (func == NULL) {
        mangleSig =
            createMangledSignature(&meth->prototype);
        if (mangleSig == NULL)
            goto bail;

        mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);
        if (mangleCMSig == NULL)
            goto bail;

        sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);

        ALOGV("+++ calling dlsym(%s)", mangleCMSig);
        func = dlsym(pLib->handle, mangleCMSig);
        if (func != NULL) {
            ALOGV("Found '%s' with dlsym", mangleCMSig);
        }
    } else {
        ALOGV("Found '%s' with dlsym", mangleCM);
    }

所以,这里我们看到了,默认的函数名映射的规则是:Java_you_pakcage_ClassName_MethodName[__methoidSig],当通过Java_you_pakcage_ClassName_MethodName找不到时,会再尝试Java_you_pakcage_ClassName_MethodName__methoidSig来查找。

在找到c函数后,通过dvmUseJNIBridge来建立联系:

void dvmUseJNIBridge(Method* method, void* func) {
    method->shouldTrace = shouldTrace(method);

    // Does the method take any reference arguments?
    method->noRef = true;
    const char* cp = method->shorty;
    while (*++cp != '\0') { // Pre-increment to skip return type.
        if (*cp == 'L') {
            method->noRef = false;
            break;
        }
    }

    DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;
    dvmSetNativeFunc(method, bridge, (const u2*) func);
}

在正常情况下,gDvmJni.useCheckJni为false,所以bridge函数为dvmCallJNIMethod:

void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) {
    u4* modArgs = (u4*) args;
    jclass staticMethodClass = NULL;

    u4 accessFlags = method->accessFlags;
    bool isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0;

    //ALOGI("JNI calling %p (%s.%s:%s):", method->insns,
    //    method->clazz->descriptor, method->name, method->shorty);

    /*
     * Walk the argument list, creating local references for appropriate
     * arguments.
     */
    int idx = 0;
    Object* lockObj;
    if ((accessFlags & ACC_STATIC) != 0) {
        lockObj = (Object*) method->clazz;
        /* add the class object we pass in */
        staticMethodClass = (jclass) addLocalReference(self, (Object*) method->clazz);
    } else {
        lockObj = (Object*) args[0];
        /* add "this" */
        modArgs[idx++] = (u4) addLocalReference(self, (Object*) modArgs[0]);
    }

    if (!method->noRef) {
        const char* shorty = &method->shorty[1];        /* skip return type */
        while (*shorty != '\0') {
            switch (*shorty++) {
            case 'L':
                //ALOGI("  local %d: 0x%08x", idx, modArgs[idx]);
                if (modArgs[idx] != 0) {
                    modArgs[idx] = (u4) addLocalReference(self, (Object*) modArgs[idx]);
                }
                break;
            case 'D':
            case 'J':
                idx++;
                break;
            default:
                /* Z B C S I -- do nothing */
                break;
            }
            idx++;
        }
    }

    if (UNLIKELY(method->shouldTrace)) {
        logNativeMethodEntry(method, args);
    }
    if (UNLIKELY(isSynchronized)) {
        dvmLockObject(self, lockObj);
    }

    ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_NATIVE);

    ANDROID_MEMBAR_FULL();      /* guarantee ordering on method->insns */
    assert(method->insns != NULL);

    JNIEnv* env = self->jniEnv;
    COMPUTE_STACK_SUM(self);
    dvmPlatformInvoke(env,
            (ClassObject*) staticMethodClass,
            method->jniArgInfo, method->insSize, modArgs, method->shorty,
            (void*) method->insns, pResult);
    CHECK_STACK_SUM(self);

    dvmChangeStatus(self, oldStatus);

    convertReferenceResult(env, pResult, method, self);

    if (UNLIKELY(isSynchronized)) {
        dvmUnlockObject(self, lockObj);
    }
    if (UNLIKELY(method->shouldTrace)) {
        logNativeMethodExit(method, self, *pResult);
    }
}

这个函数重点说一下,做了几个事情:

  1. 判断是否是静态方法,如果是,就将ClassObject的间接引用设置为第一个参数。如果不是,则将this对象的间接引用设置为第一个参数。这就和JNI方法中的参数定义对应起来了:
Java_you_package_Class_StaticMethod(JNIEnv *env, jclass type, ...) {}
Java_you_package_Class_Method(JNIEnv *env, jobject jthis, ...) {}
  1. 判断参数中是否存在引用类型的对象(非原型对象),如果有,将对象添加到局部引用表中获取其间接引用,替换参数。
  2. 调用dvmPlatformInvoke,最终就会调用到JNI方法了。dvmPlatformInvoke对不同的ABI有不同的实现。
  3. 从pResult中获取返回值,如果是间接引用,则转化为真实的对象。

b. RegisterNatives 另外一种是通过自行调用JNIEnv.RegisterNatives来完成注册:

static jint RegisterNatives(JNIEnv* env, jclass jclazz,
    const JNINativeMethod* methods, jint nMethods)
{
    ScopedJniThreadState ts(env);

    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);

    if (gDvm.verboseJni) {
        ALOGI("[Registering JNI native methods for class %s]",
            clazz->descriptor);
    }

    for (int i = 0; i < nMethods; i++) {
        if (!dvmRegisterJNIMethod(clazz, methods[i].name,
                methods[i].signature, methods[i].fnPtr))
        {
            return JNI_ERR;
        }
    }
    return JNI_OK;
}

主要是dvmRegisterJNIMethod:

static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
    const char* signature, void* fnPtr)
{
    ...
    dvmUseJNIBridge(method, fnPtr);
    ...
}

又是dvmUseJNIBridge,剩下的就和前面一样了。 所以主动注册与默认查找的区别就是,主动注册需要告诉JNI,Java方法和C函数的映射,而默认查找则按照对应的规则去查找。对后在调用逻辑上,完全一致。

同时,我们也看到了,在调用C函数前,真实的对象被转化为间接引用,然后传递到JNI方法中,同时,JNI方法返回的间接引用被转化为真实的对象,供下一步使用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

day19_java基础加强_动态代理+注解+类加载器

        Proxy Pattern(即:代理模式),23种常用的面向对象软件的设计模式之一。         代理模式的定义:为其他对象提供一种代理以控...

1104
来自专栏Java技术栈

Java 面试题经典 77 问(含答案)!

1713
来自专栏Netkiller

java 脚本引擎

本文节选自《Netkiller Java 手札》 第 18 章 java 脚本引擎 目录 18.1. Maven 18.2. Helloworld 18.3. ...

3835
来自专栏JavaEdge

Netty 源码深度解析(九) - 编码概述1 抽象类 MessageToByteEncoder2 抽象类 MessageToMessageEncoder一个java对象最后是如何转变成字节流,写到s

编码器实现了ChannelOutboundHandler,并将出站数据从 一种格式转换为另一种格式,和我们方才学习的解码器的功能正好相反。Netty 提供...

1941
来自专栏静晴轩

lua表排序

Lua作为一种很强大且轻量级脚本语言的存在,对于掌握其几乎无所不能的Table(其实就是一个Key Value的数据结构,它很像Javascript中的Obje...

42411
来自专栏北京马哥教育

awk学习笔记

awk是一种模式扫描和处理工具,相对于grep的查找,sed的编辑,它在对数据进行分析生成报表时显得尤为强大。awk通过逐行遍历一个或多个 文件的方式,查找模...

2946
来自专栏岑玉海

Hbase 学习(三)Coprocessors

Coprocessors 之前我们的filter都是在客户端定义,然后传到服务端去执行的,这个Coprocessors是在服务端定义,在客户端调用,然后在服...

39011
来自专栏Spark学习技巧

Flink DataSet编程指南-demo演示及注意事项

Flink中的DataStream程序是对数据流进行转换的常规程序(例如,过滤,更新状态,定义窗口,聚合)。数据流的最初的源可以从各种来源(例如,消息队列,套接...

3.4K12
来自专栏Android 研究

Retrofit解析8之核心解析——ServiceMethod及注解1

上篇文章已经介绍了Retrofit里面的大多数类,今天就重点介绍ServiceMethod,本片文章主要内容如下:

1994
来自专栏Taylor技术日志

关于char/varchar(n)中n的探究:字符数or字节数

很多时候我们不确定某个字段的长度,会使用varchar类型,比如某个字段定义为varchar(100),那这100的长度能存多少个中文?

4357

扫码关注云+社区