有了前面的铺垫,终于可以说说虚拟机是如何调用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.cpp的loadMethodFromDex中,我们看到:

        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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JNI实现源码分析【四 函数调用】相关推荐

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

    在JNI实现源码分析[二 数据结构]的参数传递一节中,我们提到,JNI为了安全性的考虑使用了形如jobject的结构来传递参数.而jobject被表述为指针,但又不是直接指向Object的指针那么jo ...

  2. ABP源码分析四十七:ABP中的异常处理

    ABP源码分析四十七:ABP中的异常处理 参考文章: (1)ABP源码分析四十七:ABP中的异常处理 (2)https://www.cnblogs.com/1zhk/p/5538983.html (3 ...

  3. 【投屏】Scrcpy源码分析四(最终章 - Server篇)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  4. gSOAP 源码分析(四)

    gSOAP 源码分析(四) 2012-6-2 邵盛松 前言 本文主要说明gSOAP中对Client的认证分析 gSOAP中包含了HTTP基本认证,NTLM认证等,还可以自定义SOAP Heard实现认 ...

  5. Spring 源码分析(四) ——MVC(二)概述

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) from:Spring 源码分析(四) --MVC(二)概述 - 水门-kay的个人页面 - OSCHINA ...

  6. 【转】ABP源码分析四十七:ABP中的异常处理

    ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...

  7. 【转】ABP源码分析四:Configuration

    核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...

  8. Kbuild系统源码分析(四)—./scripts/Makefile.build

    版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/lidan1 ...

  9. 谷歌chrome浏览器的源码分析(四)

    上一次说到需要调用这个OpenURLFromTab函数,那么这个函数是做什么的呢?从名称上可能猜到它是打开网页,但是是从目前TAB页里打开呢?还是新建一个?或者使用每个TAB页一个进程呢?这些疑惑,只 ...

  10. 【转】ABP源码分析四十六:ABP ZERO中的Ldap模块

    通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供Defaut ...

最新文章

  1. 感悟Windows7
  2. C++ 11 创建和使用 shared_ptr
  3. aws python lambda_AWS Lambda
  4. 神经网络 | BP神经网络介绍(附源代码:BP神经网络-异或问题)
  5. EXT核心API详解(二)-Array/Date/Function/Number/String
  6. GitHub上13个学习资源项目,值得收藏!
  7. 现代人的压力和焦虑_设计师如何建立减少焦虑和压力的体验
  8. adb不识别设备(手机)的若干情形及解决方法
  9. wget python3_python wget
  10. 通过 Visual Studio 对 SQL Server 中的存储过程设置断点并进入存储过程对其进行调试...
  11. APP开发多少钱多少人和哪些注意事项
  12. UniApp引入极光推送
  13. 技术人修炼之道阅读笔记(七)系统性思维方法
  14. 查询分析器默认代码颜色
  15. CSDN是什么???
  16. Linux的网络配置及jdk的安装
  17. NLP词向量模型总结:从Elmo到GPT,再到Bert
  18. 运营必备的神器 | C1N短网址
  19. 分享:DFC 1.1.0 发布,C/C++项目开发框架
  20. 什么是JTAG及JTAG接口简介

热门文章

  1. [Servlet]深入掌握Servlet
  2. js数据类型判断和数组判断
  3. 通过一组RESTful API暴露CQRS系统功能
  4. C语言指针实现计算平均分等功能
  5. Python matplotlib 和PIL
  6. VTK序列图像的读取
  7. 凸优化问题工具包cvxpy安装
  8. Linux C++多线程同步的四种方式
  9. 03- 网络最新流行
  10. 云炬创业政策学习笔记20210112