面试题十六:只有C、C++可以编写JNI的Native库吗?

只有C、C++可以编写JNI的Native库吗?

一、面试官视角:这道题想考察什么?

  • 是否对JNI函数绑定的原理有深入认识
  • 是否对底层开发有丰富的经验

二、题目剖析

1、只有C、C++可以编写JNI的Native库吗?

当然不是,关键就要看底层的Native程序它是个so库,编写出来之后它和C、C++其实没啥关系!

2、Native程序与Java关联的本质是什么?

关联的本质就是映射的方法,静态绑定、动态绑定等等!

JNI对Native函数的要求:

  • 静态绑定
    • 符号表可见
    • 命名符合Java Native方法的包名_类名_方法名
    • 符号名按照C语言的规则修饰(不能按照C++的方式来修饰,C++修饰完之后都不认识了)
  • 动态绑定
    • 函数本身无要求
    • JNI可识别入口函数如 JNI_OnLoad进行注册即可

3、可选的Native语言

  • Golang
  • Rust
  • Kotlin Native
  • Scala Native

4、举例如何用其他语言编写符合JNI命名规则的符合

  • 认识Kotlin Native

    • Kotlin-jvm
      • JavaFx
      • Android
      • Java Web
    • Kotlin-js
      • Front End
      • Nodejs
    • Kotlin-native
      • Android Native
      • iOS
      • Linux Embeded(嵌入式)
      • Wasm
  • Kotlin Native编写JNI Native库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Gradle:

    kotlin {
    targets {
    fromPreset(presets.androidNativeArm32, "HelloWorld")
    configure([HelloWorld]) {
    compilations.main.outputKinds 'DYNAMIC'
    }
    }
    sourceSets {
    HelloWorldMain{}
    HelloWorldTest{}
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Java:

    public class HelloJni extends AppCompatActivity {
    ...
    public native String stringFromJNI();
    public native void sayHello();
    public native String callJava();
    public String callFromNative() {
    return "This is from Java!!";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    kotlin编写的底层stringFromJNI():

    // @CName注解,其实就是静态绑定,@CName注解的作用就是在编译的时候告诉编译器我这个函数它在符号表里面长这个样子。
    @CName("Java_com_example_hellojni_HelloJni_stringFromJNI")
    // 函数名是否一致不做要求
    fun stringFromJNI(env: CPointer<JNIEnvVar>, thiz: jobject):jstring {
    // memScoped:在C里面内存是需要主动去释放及管理的,那么在kotlin Native里面它也是实现了自动管理,所以memScoped就是用来干这个的!
    memScoped {
    return env.pointed.pointed!!.NewStringUTF!!.invoke(env, "This is from Kotlin Native!!".cstr.ptr)!!
    }
    }
    1
    2
    3
    4
    5
    6
    kotlin编写的底层sayHello():

    @CName("Java_com_example_hellojni_HelloJni_sayHello")
    fun sayHello() {
    _android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "Hello %s", "Native")
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    kotlin编写的底层callJava():

    @CName("Java_com_example_hellojni_HelloJni_callJava")
    fun callJava(env: CPointer<JNIEnvVar>, thiz: jobject):jstring {
    val jniEnvVal = env.pointed.pointed!!
    val jclass = jniEnvVal.GetObjectClass!!.invoke(env, thiz)
    val methodId = jniEnvVal.GetMethodID!!.invoke(env, jclass, "callFromNative".cstr.ptr, "()Ljava/lang/String;".cstr.ptr)
    return jniEnvVal.CallObjectMethodA!!.invoke(env, thiz, methodId, null) as jstring
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    kotlin编写的底层JNI_OnLoad:(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)
    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 = "sayHello".cstr.ptr
    jniMethod[0].signature = "()V".cstr.ptr
    env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)
    }
    JNI_VERSION_1_6
    }
    }