专栏首页BennyhuoKotlin Native 写 Jni 第二弹:动态绑定 Native 方法

Kotlin Native 写 Jni 第二弹:动态绑定 Native 方法

上一篇文章 我讲了用 @CName 这个神奇的注解,可以配置 Kotlin Native 函数在符号表中的名字,进而根据 Jni 静态绑定的规则来对应到 Java native 方法,但实际开发当中我们更喜欢用动态注册的方式,因为一方面不受名字的约束,不影响代码重构,函数名也相对美观,另一方面调用起来也相对高效,节省了静态绑定的查找过程。

如果大家习惯用 C 写动态绑定的代码,那么 Kotlin Native 写起来思路也是很简单的,只要依样画葫芦,就可以写出来,我们先给出代码:

@CName("JNI_OnLoad")
fun JNI_OnLoad(vm: CPointer<JavaVMVar>, preserved: COpaquePointer): jint {
    return memScoped {
        val envStorage = alloc<CPointerVar<JNIEnvVar>>()
        val vmValue = vm.pointed.pointed!!
        val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)
        __android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "JNI_OnLoad")
        if(result == JNI_OK){
            val env = envStorage.pointed!!.pointed!!
            val jclass = env.FindClass!!(envStorage.value, "com/example/hellojni/HelloJni".cstr.ptr)

            val jniMethod = allocArray<JNINativeMethod>(1)
            jniMethod[0].fnPtr = staticCFunction(::sayHello2)
            jniMethod[0].name = "sayHello2".cstr.ptr
            jniMethod[0].signature = "()V".cstr.ptr
            env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)

            __android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "register say hello2, %d, %d", sizeOf<CPointerVar<JNINativeMethod>>(), sizeOf<JNINativeMethod>())
        }
        JNI_VERSION_1_6
    }
}

思路很简单,就是先通过 CName 注解搞定 JNI_OnLoad 函数,让 Java 虚拟机能够在加载 so 库的时候找到这个入口函数,那么我们接下来就是纯调用 Jni 的 C 接口了。

再说下 memScope 这个东西,C 当中内存管理是人工不智能的,Kotlin Native 则有自己的内存管理机制,因此如果我们需要在 Kotlin Native 当中访问 C 接口,并且创建 C 变量,就需要通过 memScope 来提醒 Kotlin Native 这些变量需要来统一管理。

获取 JNIEnv 的指针时我们首先构造了一个指针的左值类型:

val envStorage = alloc<CPointerVar<JNIEnvVar>>()

这么说有些奇怪,总之在 C 的指针类型向 Kotlin Native 映射时, CPointer 的左值类型会映射成 CPointerVar,我现在对 Kotlin Native 与 C 的交互还没有仔细研究,就暂时不展开说了,等后面有机会再系统介绍 Kotlin Native 的细节。

接下来我们看这句:

val vmValue = vm.pointed.pointed!!

C 版本的定义 JavaVM 其实本身也是一个指针:

typedef const struct JNIInvokeInterface* JavaVM;

因此两个 pointed 的调用相当于获取到了 JNIInvokeInterface 这个结构体,于是后面我们就可以用它持有的函数指针进行获取 JNIEnv 的操作了:

val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)

再稍微提一个事儿,那就是这些类型从 C 的角度映射过来,空类型安全自然是无法保证的,因此我们会见到各种 !! 的使用,这样实际上对于开发来讲非常不友好。因此理想的状况是,我们用 Kotlin Native 对 C 接口进行封装,将这些底层的工作按照 Kotlin 的风格进行转换,这样我们使用起来就会容易得多——官方的 AndroidNativeActivity 的例子当中提供了 JniBridge 及一系列的类其实就是做了这样一件事儿,只不过还不算太完整。

接下来我们要实现动态绑定了:

val jclass = env.FindClass!!(envStorage.value, "com/example/hellojni/HelloJni".cstr.ptr)
val jniMethod = allocArray<JNINativeMethod>(1)
jniMethod[0].fnPtr = staticCFunction(::sayHello2)
jniMethod[0].name = "sayHello2".cstr.ptr
jniMethod[0].signature = "()V".cstr.ptr
env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)

这里面也向大家展示了如何将 Kotlin 函数转为 C 的函数指针,总体来讲思路还是很简单的,毕竟我们只是照猫画虎。

问题也是很显然的,如果你也尝试这样做了,一定被这些映射过来的接口函数的签名给搞晕过:

public final var RegisterNatives: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlinx.cinterop.CPointer<platform.android.JNIEnvVar /* = kotlinx.cinterop.CPointerVarOf<platform.android.JNIEnv /* = kotlinx.cinterop.CPointer<platform.android.JNINativeInterface> */> */>?, platform.android.jclass? /* = kotlinx.cinterop.CPointer<out kotlinx.cinterop.CPointed>? */, kotlinx.cinterop.CPointer<platform.android.JNINativeMethod>?, platform.android.jint /* = kotlin.Int */) -> platform.android.jint /* = kotlin.Int */>>? /* compiled code */

这其实就是 RegisterNatives 这个函数指针的签名,它接受 JNIEnv 的值,jclass,以及一个 JNINativeMethod 结构体的数组和这个数组的长度作为参数,但我们点进去看源码或者看函数前面却需要看这么一大堆东西,直接晕菜。

这其实也是目前 Kotlin Native 比较麻烦的问题之一:开发体验。尽管 1.0-Beta 出来之后,相比过去要好了许多,但开发体验似乎仍然有待提高,这其实也会直接影响开发者的涌入。

简单来说,这篇文章没什么太大的技术含量,只是对上一篇文章的一个补充。

本文涉及源码参见:hello-kni / https://github.com/enbandari/hello-kni


本文分享自微信公众号 - Kotlin(KotlinX),作者:Benny Huo

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-31

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 让 Kotlin 为数据科学做好准备

    今年在 2019 年 KotlinConf 上,Roman Belov 概述了 Kotlin 的数据科学方法。既然该演讲现在已公开,我们决定重述一下,并分享一些...

    bennyhuo
  • 闲聊 Kotlin-Native (0) - 我们为什么应该关注一下 Kotlin Native?

    Kotlin-Native 的定位略显尴尬,为什么这么说呢?因为现在的编程语言实在太多了,新语言出来必然要解决现有某个语言的痛点,这样才能快速切入该语言所覆盖的...

    bennyhuo
  • Kotlin 1.4 现已发布,专注于质量和性能

    Kotlin 1.4.0 今日发布!在过去的几年里,我们一直在努力使 Kotlin 成为一种有趣、令人愉快且高效的编程语言。为了借助此版本的 Kotlin 继续...

    bennyhuo
  • 4.4 文件系统疑难点 3-4

    为了创建一个文件,应用程序调用逻辑文件系统。逻辑文件系统知道目录结构形式。它将分配一个新的FCB给文件,把相应目录读入内存,用新的文件名更新该目录和FCB,并将...

    week
  • 非正常关闭 vi 编辑器产生 swp 文件怎么删除

    魏艾斯博客www.vpsss.net
  • Python文件IO操作

    open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, clo...

    py3study
  • #PY小贴士# 我的文件为何无法写入

    1. 搞错了当前目录,自以为是在某个目录下,其实不是。此情况易发于使用 IDE 的时候,因为 IDE 的执行目录并不一定是当前 py 文件所在目录。可以通过 p...

    Crossin先生
  • 如何解决数据文件传输的风险?

    系统底层的重要、核心数据文件时常面临着更新和传输,仅仅依靠防止拷贝数据文件是无法避免事故的发生,也无法快速定位事故原因,更加无法及时恢复灾难。 那么“快速...

    安恒信息
  • 使用CNVkit进行CNV分析

    CNVkit是一款CNV预测软件,适用于全外显子,目的区域靶向测序等数据的CNV检测,官网如下

    生信修炼手册
  • 20120918-LIST类定义《数据结构与算法分析》

    LIST类结构 1 template <typename Object> 2 class List 3 { 4 private: 5...

    用户1154259

扫码关注云+社区

领取腾讯云代金券