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, ...) {}
  2. 判断参数中是否存在引用类型的对象(非原型对象),如果有,将对象添加到局部引用表中获取其间接引用,替换参数。
  3. 调用dvmPlatformInvoke,最终就会调用到JNI方法了。dvmPlatformInvoke对不同的ABI有不同的实现。
  4. 从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方法返回的间接引用被转化为真实的对象,供下一步使用。



作者:difcareer
链接:http://www.jianshu.com/p/1ef556aec1cd
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值