JNI_OnLoad函数不存在的问题

原创
2015/10/27 11:57
阅读数 5.7K

今天分析一个app的老版本时,无意发现JNI_OnLoad不存在,但是so的确是java层load加载,各native函数也有声明和调用,以为又遇到什么黑科技。查找发现最早期的ndk开发版本中的确是没有这个函数的。


现期各版本的ndk中JNI_OnLoad函数都是load时自动调用的,如果未发现则去调用dvmResolveNativeMethod。以下时一份详细的流程解释。



以下摘自 http://yanbober.github.io/2015/02/25/android_studio_jni_3/


Android OS加载JNI Lib的方法有两种:

  • 通过JNI_OnLoad。

  • 如果JNI Lib实现中没有定义JNI_OnLoad,则dvm调用dvmResolveNativeMethod进行动态解析。

PS:咱们上面第一部分就是dvm调用dvmResolveNativeMethod进行动态解析,所以log打印No JNI_OnLoad found。

从网上查到的深入解析(此解析模块代码引用自网络)

JNI_OnLoad机制分析

System.loadLibrary调用流程如下所示:

System.loadLibrary->Runtime.loadLibrary->(Java)nativeLoad->(C: java_lang_Runtime.cpp)Dalvik_java_lang_Runtime_nativeLoad->dvmLoadNativeCode->(dalvik/vm/Native.cpp)

接着如下:

  • dlopen(pathName, RTLD_LAZY) (把.so mmap到进程空间,并把func等相关信息填充到soinfo中)

  • dlsym(handle, “JNI_OnLoad”)

  • JNI_OnLoad->RegisterNatives->dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName, const char* signature, void* fnPtr)->dvmUseJNIBridge(method, fnPtr)->(method->nativeFunc = func)

JNI函数在进程空间中的起始地址被保存在ClassObject->directMethods中。

struct ClassObject : Object {       /* static, private, and <init> methods */       int             directMethodCount;       Method*         directMethods;          /* virtual methods defined in this class; invoked through vtable */       int             virtualMethodCount;       Method*         virtualMethods;   }

此ClassObject通过gDvm.jniGlobalRefTable或gDvm.jniWeakGlobalRefLock获取。

dvmResolveNativeMethod延迟解析机制

如果JNI Lib中没有JNI_OnLoad,即在执行System.loadLibrary时,无法把此JNI Lib实现的函数在进程中的地址增加到ClassObject->directMethods。则直到需要调用的时候才会解析这些javah风格的函数 。这样的函数dvmResolveNativeMethod(dalvik/vm/Native.cpp)来进行解析,其执行流程如下所示:

void dvmResolveNativeMethod(const u4* args, JValue* pResult, const Method* method, Thread* self)->(Resolve a native method and invoke it.)

接着如下:

  • void* func = lookupSharedLibMethod(method)(根据signature在所有已经打开的.so中寻找此函数实现)dvmHashForeach(gDvm.nativeLibs, findMethodInLib,(void*) method)->findMethodInLib(void* vlib, void* vmethod)->dlsym(pLib->handle, mangleCM)

  • dvmUseJNIBridge((Method*) method, func)

  • (*method->nativeFunc)(args, pResult, method, self);(调用执行)

说完蛋疼Load基础后该准么办?

答案其实就是推荐Android OS加载JNI Lib的方法的通过JNI_OnLoad。因为通过它你可以干许多自定义的事,譬如实现自己的本地注册等。 因为在上面的解析中已经看到了JNI_OnLoad->RegisterNatives->…这两个关键方法。具体细节咱们现在再说说。

先来看JNI_OnLoad函数

JNI_OnLoad()函数主要的用途有两点:

  • 通知VM此C组件使用的JNI版本。如果你的.so文件没有提供JNI_OnLoad()函数,VM会默认该.so使用最老的JNI 1.1版本。 而新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer, 就必须藉由JNI_OnLoad()函数来告知VM。

  • 因为VM执行到System.loadLibrary()函数时,会立即先调运JNI_OnLoad(),所以C组件的开发者可以由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization)。

既然有JNI_OnLoad(),那就有相呼应的函数,那就是JNI_OnUnload(),当VM释放JNI组件时会呼叫它,因此在该方法中进行善后清理,资源释放的动作最为合适。

再来看RegisterNatives函数

在上面第一部分时我们看见通过javah命令生成的io_github_yanbober_ndkapplication_NdkJniUtils.h里函数的名字好长,看着就蛋疼。你肯定也想过怎么这么长, 而且当有时候项目需求原因导致类名变了的时候,函数名必须一个一个的改,更加蛋疼。我第一次接触时那时候自己经验不足,就遇上了这个蛋疼问题。泪奔啊!

既然这样那就有解决办法的,那就是RegisterNatives大招。接下来来看下这个大招:

App的Java程序寻找c本地方法的过程一般是依赖VM去寻找*.so里的本地函数,如果需要连续调运很多次,每次都要寻找一遍, 会多花许多时间。因此为了解决这个问题我们可以自行将本地函数向VM进行登记,然后让VM自行调registerNativeMethods()函数。

VM自行调registerNativeMethods()函数的作用主要有两点:  

  • 更加有效率去找到C语言的函数  

  • 可以在执行期间进行抽换,因为自定义的JNINativeMethod类型的methods[]数组是一个名称-函数指针对照表,在程序执行时, 可以多次调运registerNativeMethods()函数来更换本地函数指针,从而达到弹性抽换本地函数的效果。

上面提到的JNINativeMethod结构是c/c++方法和Java方法之间映射关系的关键结构,该结构定义在jni.h中,具体定义如下:

typedef struct {    const char* name;//java方法名称    const char* signature; //java方法签名   void*       fnPtr;//c/c++的函数指针   } JNINativeMethod;

所谓自定义的JNINativeMethod类型的methods[]数组自然也就类似长下面这样了:

static JNINativeMethod methods[] = {   {"generateKey", "(Ljava/lang/String;)Ljava/lang/String;", (void*)generateKey},   };

以上也就是所谓的动态注册JNI了。


展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部