Skip to content

Commit

Permalink
Share object between JNI and JVM in sample app
Browse files Browse the repository at this point in the history
  • Loading branch information
LandryNorris committed Jul 14, 2022
1 parent 048454b commit 046f92d
Show file tree
Hide file tree
Showing 15 changed files with 124 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package io.github.landrynorris.jniutils
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.invoke
import kotlinx.cinterop.pointed
import platform.android.JNIEnvVar
import platform.android.jfieldID
import platform.android.jobject
import platform.android.*

fun CPointer<JNIEnvVar>.getObjectField(obj: jobject, fieldId: jfieldID?): jobject? {
val method = pointed.pointed?.GetObjectField ?: error("JNI is not Oracle standard")
Expand Down Expand Up @@ -51,3 +49,44 @@ fun CPointer<JNIEnvVar>.getDoubleField(obj: jobject, fieldId: jfieldID?): Double
val method = pointed.pointed?.GetDoubleField ?: error("JNI is not Oracle standard")
return method.invoke(this, obj, fieldId)
}

fun CPointer<JNIEnvVar>.getBooleanField(obj: jobject, name: String, clazz: jclass): Boolean {
return getBooleanField(obj, getFieldId(clazz, name, Boolean.toString()))
}

fun CPointer<JNIEnvVar>.getByteField(obj: jobject, name: String, clazz: jclass): Byte {
return getByteField(obj, getFieldId(clazz, name, Byte.toString()))
}

fun CPointer<JNIEnvVar>.getShortField(obj: jobject, name: String, clazz: jclass): Short {
return getShortField(obj, getFieldId(clazz, name, Short.toString()))
}

fun CPointer<JNIEnvVar>.getCharField(obj: jobject, name: String, clazz: jclass): Char {
return getCharField(obj, getFieldId(clazz, name, Char.toString()))
}

fun CPointer<JNIEnvVar>.getIntField(obj: jobject, name: String, clazz: jclass): Int {
return getIntField(obj, getFieldId(clazz, name, Int.toString()))
}

fun CPointer<JNIEnvVar>.getLongField(obj: jobject, name: String, clazz: jclass): Long {
return getLongField(obj, getFieldId(clazz, name, Long.toString()))
}

fun CPointer<JNIEnvVar>.getFloatField(obj: jobject, name: String, clazz: jclass): Float {
return getFloatField(obj, getFieldId(clazz, name, Float.toString()))
}

fun CPointer<JNIEnvVar>.getDoubleField(obj: jobject, name: String, clazz: jclass): Double {
return getDoubleField(obj, getFieldId(clazz, name, Double.toString()))
}

fun CPointer<JNIEnvVar>.getObjectField(obj: jobject, name: String, clazz: jclass, signature: String): jobject? {
return getObjectField(obj, getFieldId(clazz, name, signature))
}

fun CPointer<JNIEnvVar>.getStringField(obj: jobject, name: String, clazz: jclass): String {
val field = getObjectField(obj, name, clazz, String.toString()) as jstring
return getString(field)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fun CPointer<JNIEnvVar>.registerNatives(clazz: jclass, methods: List<JNINativeMe
name = m.name
signature = m.signature
fnPtr = m.fnPtr
println("Signature is ${m.signature}")
println("Signature is ${m.signature?.toKString()}")
}
method.invoke(this@registerNatives, clazz, methodBuffer, methods.size)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.landrynorris.jniutils

import platform.android.jobject
import platform.android.jstring
import kotlin.reflect.KClass
import kotlin.reflect.KType
Expand All @@ -11,6 +12,8 @@ data class Signature(val parameterClasses: List<JClass>, val returnClass: JClass
}
}

inline fun <reified T> signature() = classToSignature(typeOf<T>())

inline fun classToSignature(type: KType) = when(type) {
typeOf<Unit>() -> Void
typeOf<Byte>() -> Byte
Expand All @@ -21,15 +24,14 @@ inline fun classToSignature(type: KType) = when(type) {
typeOf<Long>() -> Long
typeOf<Float>() -> Float
typeOf<Double>() -> Double
typeOf<jstring>() -> String
typeOf<BooleanArray>() -> BooleanArray
typeOf<ByteArray>() -> ByteArray
typeOf<ShortArray>() -> ShortArray
typeOf<CharArray>() -> CharArray
typeOf<IntArray>() -> IntArray
typeOf<FloatArray>() -> FloatArray
typeOf<DoubleArray>() -> DoubleArray
typeOf<jobject>() -> createSignature("java.lang.Object")
typeOf<jstring>() -> String
else -> createSignature(type.toString())
}


2 changes: 2 additions & 0 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ kotlin {
}

sourceSets {
val commonMain by getting
val ndkMain by creating {
dependsOn(commonMain)
dependencies {
implementation(project(":jni-utils"))
//implementation("io.github.landrynorris:jni-utils:0.0.1-alpha02")
Expand Down
Binary file modified sample/src/androidMain/jniLibs/arm64-v8a/libsample.so
Binary file not shown.
Binary file modified sample/src/androidMain/jniLibs/armeabi-v7a/libsample.so
Binary file not shown.
Binary file modified sample/src/androidMain/jniLibs/x86/libsample.so
Binary file not shown.
Binary file modified sample/src/androidMain/jniLibs/x86_64/libsample.so
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.landrynorris.sample

import io.github.landrynorris.jni.sample.SharedClass

object JniBridge {
init {
println("Loading library for JNI")
Expand All @@ -8,6 +10,8 @@ object JniBridge {
}

external fun methodWithParameters(value: Int)
external fun handleShared(sharedClass: SharedClass): String
//external fun doubleAll(array: DoubleArray): DoubleArray
external fun callJavaFunction(value: Double)
external fun buttonClicked(ptr: Long)
external fun getText(ptr: Long): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import io.github.landrynorris.jni.sample.DataHolder
import io.github.landrynorris.jni.sample.SharedClass
import io.github.landrynorris.sample.ui.theme.JniUtilsTheme

class MainActivity: AppCompatActivity() {
Expand All @@ -27,8 +29,17 @@ class MainActivity: AppCompatActivity() {
findViewById<Button>(R.id.btnCrash).setOnClickListener {
JniBridge.crash("App requested a Fatal Error")
}

//findViewById<TextView>(R.id.doubleValuesLabel).text = getDoubleValuesText()
findViewById<TextView>(R.id.doubleValuesLabel).text = JniBridge.handleShared(
SharedClass(0.5, DataHolder(5, 2.5, "multiplied values"))
)
}

// private fun getDoubleValuesText(): String {
// return JniBridge.doubleAll(doubleArrayOf(0.0, 0.5, 2.0, -3.0)).joinToString(", ")
// }

private fun testJni() {
JniBridge.methodWithParameters(50)
JniBridge.callJavaFunction(12.5)
Expand Down
8 changes: 8 additions & 0 deletions sample/src/androidMain/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@
android:text="Crash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<TextView android:id="@+id/doubleValuesLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<TextView android:id="@+id/sharedClassLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.landrynorris.jni.sample

class SharedClass(val x: Double, val dataHolder: DataHolder)

data class DataHolder(val i: Int, val d: Double, val s: String)
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ fun callJavaFunction(env: CPointer<JNIEnvVar>, thiz: jobject, value: Double) {
}
}

fun doubleAll(env: CPointer<JNIEnvVar>, thiz: jobject, array: DoubleArray): DoubleArray {
return array.map { it*2 }.toDoubleArray()
}

fun createRepository(env: CPointer<JNIEnvVar>, thiz: jobject): Long {
val repository = ButtonClickRepository()
println("Converting repository to pointer")
Expand All @@ -45,6 +49,13 @@ fun getText(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long): jstring {
return repository.getText().toJString(env) ?: error("Unable to create JString")
}

fun handleSharedClass(env: CPointer<JNIEnvVar>, thiz: jobject, data: jobject): jstring {
val shared = data.toSharedClass(env)!!
val total = shared.x * shared.dataHolder.i * shared.dataHolder.d
val s = shared.dataHolder.s + ": " + total
return s.toJString(env) ?: error("Unable to create JString")
}

fun crash(env: CPointer<JNIEnvVar>, thiz: jobject, message: jstring) {
val messageString = env.getString(message)
env.fatalError(messageString)
Expand Down Expand Up @@ -73,7 +84,7 @@ fun registerJniNatives(env: CPointer<JNIEnvVar>) {
}

method("getText") {
signature = signature(::getText)
signature = Signature(listOf(Long), String).toString()
function = staticCFunction(::getText)
}

Expand All @@ -91,6 +102,16 @@ fun registerJniNatives(env: CPointer<JNIEnvVar>) {
signature = Signature(listOf(String)).toString()
function = staticCFunction(::crash)
}

// method("doubleAll") {
// signature = signature(::doubleAll)
// function = staticCFunction(::doubleAll)
// }

method("handleShared") {
signature = Signature(listOf(signature<SharedClass>()), String).toString()
function = staticCFunction(::handleSharedClass)
}
}
println("Finished registering native methods")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.landrynorris.jni.sample

import io.github.landrynorris.jniutils.*
import kotlinx.cinterop.CPointer
import platform.android.JNIEnvVar
import platform.android.jobject

fun jobject.toSharedClass(env: CPointer<JNIEnvVar>): SharedClass? {
val clazz = env.findClass("io/github/landrynorris/jni/sample/SharedClass") ?: error("Unable to find class")
if(!env.isInstanceOf(this, clazz)) return null
val x = env.getDoubleField(this, "x", clazz)
val holder = env.getObjectField(this, "dataHolder", clazz, signature<DataHolder>().toString())
return SharedClass(x, holder?.toDataHolder(env)!!)
}

fun jobject.toDataHolder(env: CPointer<JNIEnvVar>): DataHolder? {
val clazz = env.findClass("io/github/landrynorris/jni/sample/DataHolder") ?: error("Unable to find class")
if(!env.isInstanceOf(this, clazz)) return null
val i = env.getIntField(this, "i", clazz)
val d = env.getDoubleField(this, "d", clazz)
val s = env.getStringField(this, "s", clazz)

return DataHolder(i, d, s)
}

0 comments on commit 046f92d

Please sign in to comment.