Hey, I want to write the C parts of JNI with Kotli...
# kotlin-native
h
Hey, I want to write the C parts of JNI with Kotlin Native. The JNINativeInterface has the following function to get the string in C: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetStringUTFChars This function is mapped as property in Kotlin:
var GetStringUTFChars: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<
in
JNINativeInterface
. But getting this "function" fails and I don't know why. I don't invoke the function yet.
Copy code
package sample

import jni.*
import kotlinx.cinterop.*

@CName("Java_Main_hello")
fun hello(env: JNIEnv, obj: jobject, from: jstring) {
    val getStringUTFChars = requireNotNull(env.pointed.GetStringUTFChars) {
        "GET FAILED"
    }
    val cString = getStringUTFChars(env.reinterpret(), from, null)!!
    val kString = cString.toKString()
    env.pointed.ReleaseStringUTFChars!!(env.reinterpret(), from, cString)
    println("HELLO FROM JNI: $kString")
}
l
What do you mean ‘fails’? Does the IDE not recognize it? Is it a runtime error?
h
Calling it from JVM fails:
Copy code
Uncaught Kotlin exception: kotlin.IllegalArgumentException: GET FAILED
    at 0   libhelloc.dylib                     0x11f7955f3        kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 67 
    at 1   libhelloc.dylib                     0x11f7956f7        kfun:kotlin.IllegalArgumentException#<init>(kotlin.String?){} + 55 
    at 2   libhelloc.dylib                     0x11f7ae533        kfun:sample#hello(kotlinx.cinterop.CPointer<jni.JNINativeInterface_>;kotlinx.cinterop.CPointer<cnames.structs._jobject>;kotlinx.cinterop.CPointer<cnames.structs._jobject>){} + 583 
    at 3   libhelloc.dylib                     0x11f7cd3d7        Java_Main_hello + 55 
    at 4   ???
Caller:
Copy code
fun main() {
    Main().hello("Hello from Kotlin")
}

class Main {
    init {
        System.loadLibrary("helloc")
    }

    external fun hello(from: String)
}
l
I believe you need to pass in CPointer<JNIEnvVar> to the C function.
h
I do when invoking the function
But it crashes already when "getting" the function
l
Copy code
@CName("Java_io_github_landrynorris_sample_JniBridge_methodWithParameters")
fun methodWithParameters(env: CPointer<JNIEnvVar>, thiz: jobject, intValue: Int) {
    println("Got int from JVM. Value is $intValue")
}
Here’s how I define a function that does get called.
h
Yeah, primitives are mapped and work out of the box, strings are objects and needs to convert to CPointer<ByteVar>
l
Interesting. I wonder why the JniEnv can’t find that function. What JDK are you using?
h
Locally JDK Zulu 17.0.3 on aarch64 (Apple M1), but the CI https://github.com/hfhbd/kotlinJni/runs/7284771325?check_suite_focus=true also fails
l
All the JNIEnv methods are defined in the JDK spec. I believe they’re required.
Have you tried a different JDK?
h
Yes by using another pc with windows using amazon corretto. (and the ci uses adopt jdk)
l
Interesting. I know that it works on Android, as I’ve used it in production with no issues.
h
strings too? 😞
l
I don’t see anything in Crashlytics indicated this has ever failed:
Copy code
val getChars = env.pointed.pointed?.GetStringUTFChars ?: error("Unable to get function to convert jstring")
    val cPath = getChars(env, path, null) ?: error("Unable to get chars from jstring")
I noticed that your include path is for 17.0.3, have you tried 11 or 8?
h
Wait, you use
env.pointed.pointed
l
I take in CPointer<JniEnvVar> instead of directly JniEnv
So I need an extra pointed
h
Why? Did you change the function parameter?
l
Yes. My function signature looks like
Copy code
@CName("Java_io_github_landrynorris_sample_JniBridge_methodWithParameters")
fun methodWithParameters(env: CPointer<JNIEnvVar>, thiz: jobject, intValue: Int)
h
Wow, this works!!
Thanks!
l
Keep in mind that JNI isn’t going to help you much with mistakes. That’s why I’m currently working on a library making it easier.
h
Yeah, already noticed it 😄 That would be nice!
n
@Landry Norris I’m doing the same 😄 would you share what you have in mind? Assuming it will be open source
l
Here is the repo I’m working on now https://github.com/LandryNorris/JniUtils I haven’t pushed my work from the past few days yet. I currently just have the functions from jni.h defined as extension functions on an env. I’m working on a DSL for registerNatives right now, and the end goal is to write a compiler plugin to auto-generate either a JVM bridge class from K/N methods or to go the other way and generate K/N methods from a JVM bridge class. This will take a while, though, since I’ve never made a compiler plugin before.
n
Nice! I have kind of the same plans here. A bit different design goal - you make JNI powerful and easy to use, while I want to completely hide it and do everything under the hood. We’ll see, I’ll post somewhere in this slack once I have it!
l
I'll have to take a look at your library when you post it. The reason I designed it as I did was because I generally prefer light wrappers, since they're easier to learn when you already understand the topic. It would be cool to see something that fully hides the JNI. Are you planning a compiler plugin or something?
n
I can say I never understood JNI 😄 I check online how to, whatever, make a string and a few days later I will forget, never learn. Yes, I plan a compiler plugin processing the backend (K/N) files. Still not 100% sure about the jvm side, I have a couple ideas but need to test
The main design question is what’s the source of truth - is it K/N, or jvm, or some kind of external representation. I’m going with K/N just because it fits better my own usecase
l
Yeah. I had the same question.
The issue with K/N is params. You'd still need to make sure to get the CPointer<JNIEnvVar>
177 Views