Skip to content

Commit

Permalink
Documentation and added crash method
Browse files Browse the repository at this point in the history
  • Loading branch information
LandryNorris committed Jul 12, 2022
1 parent da59769 commit 3b89167
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 14 deletions.
32 changes: 32 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,36 @@ memScoped {
myString.toJString(env)
```

Registering natives
-------------------

This library provides a DSL for registering JNI methods.

```kotlin
env.registerNatives {
clazz = env.findClass("io.github.landrynorris.sample.JniBridge".signature())

method {
name = "buttonClicked"
signature = signature(::buttonClicked)
function = staticCFunction(::buttonClicked)
}

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

You must provide a clazz value in the DSL.

There are two ways of defining the signature:

1. the Signature() constructor. Provide a list of parameter
signatures and a single return signature. The Signature#toString()
method creates a signature that the JNI can recognize.
2. For simple methods (jstring, jobject, etc. are not currently
supported), you can use the signature(Function) method to
automatically create a signature.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package io.github.landrynorris.jniutils
import kotlinx.cinterop.*
import platform.android.*

/**
* Get the [jmethodID] of a static method by its name, signature and class.
*/
fun CPointer<JNIEnvVar>.getStaticMethodId(clazz: jclass, name: String, signature: String): jmethodID? {
val method = pointed.pointed?.GetStaticMethodID ?: error("JNI is not Oracle standard")
return name.encodeToByteArray().usePinned { pinnedName ->
Expand All @@ -13,6 +16,9 @@ fun CPointer<JNIEnvVar>.getStaticMethodId(clazz: jclass, name: String, signature
}
}

/**
* Call a static method on a Java class that returns an object
*/
fun CPointer<JNIEnvVar>.callStaticObjectMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): jobject? {
val method = pointed.pointed?.CallStaticObjectMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -21,6 +27,9 @@ fun CPointer<JNIEnvVar>.callStaticObjectMethod(clazz: jclass, methodId: jmethodI
}
}

/**
* Call a static method on a Java class that returns an [Int]
*/
fun CPointer<JNIEnvVar>.callStaticIntMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Int {
val method = pointed.pointed?.CallStaticIntMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -29,6 +38,9 @@ fun CPointer<JNIEnvVar>.callStaticIntMethod(clazz: jclass, methodId: jmethodID,
}
}

/**
* Call a static method on a Java class that returns a [Long]
*/
fun CPointer<JNIEnvVar>.callStaticLongMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Long {
val method = pointed.pointed?.CallStaticLongMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -37,6 +49,9 @@ fun CPointer<JNIEnvVar>.callStaticLongMethod(clazz: jclass, methodId: jmethodID,
}
}

/**
* Call a static method on a Java class that returns a [Double]
*/
fun CPointer<JNIEnvVar>.callStaticDoubleMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Double {
val method = pointed.pointed?.CallStaticDoubleMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -45,6 +60,9 @@ fun CPointer<JNIEnvVar>.callStaticDoubleMethod(clazz: jclass, methodId: jmethodI
}
}

/**
* Call a static method on a Java class that returns a [Float]
*/
fun CPointer<JNIEnvVar>.callStaticFloatMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Float {
val method = pointed.pointed?.CallStaticFloatMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -53,6 +71,9 @@ fun CPointer<JNIEnvVar>.callStaticFloatMethod(clazz: jclass, methodId: jmethodID
}
}

/**
* Call a static method on a Java class that returns a [Short]
*/
fun CPointer<JNIEnvVar>.callStaticShortMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Short {
val method = pointed.pointed?.CallStaticShortMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -61,6 +82,9 @@ fun CPointer<JNIEnvVar>.callStaticShortMethod(clazz: jclass, methodId: jmethodID
}
}

/**
* Call a static method on a Java class that returns a [Char]
*/
fun CPointer<JNIEnvVar>.callStaticCharMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Char {
val method = pointed.pointed?.CallStaticCharMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -69,6 +93,9 @@ fun CPointer<JNIEnvVar>.callStaticCharMethod(clazz: jclass, methodId: jmethodID,
}.toInt().toChar()
}

/**
* Call a static method on a Java class that returns a [Byte]
*/
fun CPointer<JNIEnvVar>.callStaticByteMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Byte {
val method = pointed.pointed?.CallStaticByteMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand All @@ -77,6 +104,9 @@ fun CPointer<JNIEnvVar>.callStaticByteMethod(clazz: jclass, methodId: jmethodID,
}
}

/**
* Call a static method on a Java class that returns a [Boolean]
*/
fun CPointer<JNIEnvVar>.callStaticBooleanMethod(clazz: jclass, methodId: jmethodID, vararg args: jvalue): Boolean {
val method = pointed.pointed?.CallStaticBooleanMethodA ?: error("JNI is not Oracle standard")
return memScoped {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,59 @@ import platform.android.JNIEnvVar
import platform.android.jclass
import platform.android.jthrowable

/**
* Throw a JVM exception.
*/
fun CPointer<JNIEnvVar>.throwException(obj: jthrowable): Boolean {
val method = pointed.pointed?.Throw ?: error("JNI is not Oracle standard")
return method.invoke(this, obj) == 0 //Returns 0 if successful
}

/**
* Create a new exception and throw it
*/
fun CPointer<JNIEnvVar>.throwNew(jclazz: jclass, message: String): Boolean {
val method = pointed.pointed?.ThrowNew ?: error("JNI is not Oracle standard")
return message.encodeToByteArray().usePinned {
method.invoke(this, jclazz, it.addressOf(0)) == 0 //Returns 0 if successful
}
}

/**
* Get the current exception if it exists, or null if none is active.
*/
fun CPointer<JNIEnvVar>.exceptionOccurred(): jthrowable? {
val method = pointed.pointed?.ExceptionOccurred ?: error("JNI is not Oracle standard")
return method.invoke(this)
}

/**
* Print the current exception to stderr
*/
fun CPointer<JNIEnvVar>.describeException() {
val method = pointed.pointed?.ExceptionDescribe ?: error("JNI is not Oracle standard")
return method.invoke(this)
}

/**
* Clear the current exception
*/
fun CPointer<JNIEnvVar>.clearException() {
val method = pointed.pointed?.ExceptionClear ?: error("JNI is not Oracle standard")
return method.invoke(this)
}

/**
* Check if there is an active Exception in the Java layer
*/
fun CPointer<JNIEnvVar>.exceptionCheck(): Boolean {
val method = pointed.pointed?.ExceptionCheck ?: error("JNI is not Oracle standard")
return method.invoke(this).toBoolean()
}

/**
* Raise a Fatal Error directly. The VM will not recover. This method does not return.
*/
fun CPointer<JNIEnvVar>.fatalError(message: String) {
val method = pointed.pointed?.FatalError ?: error("JNI is not Oracle standard")
message.encodeToByteArray().usePinned {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class JNIMethodScope {

var clazz: jclass? = null

fun method(block: JNIMethod.() -> Unit) {
fun method(name: String, block: JNIMethod.() -> Unit) {
val method = JNIMethod()
method.block()
methods.add(method)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.github.landrynorris.jniutils
import platform.android.jstring
import kotlin.reflect.KClass

data class Signature(val parameterClasses: List<JClass>, val returnClass: JClass) {
data class Signature(val parameterClasses: List<JClass>, val returnClass: JClass = Void) {
override fun toString(): String {
return "(${parameterClasses.joinToString(separator = "")})$returnClass"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ fun CPointer<JNIEnvVar>.getStringChars(string: jstring): CPointer<jcharVar>? {
return method.invoke(this, string, null)
}

fun CPointer<JNIEnvVar>.getString(string: jstring): String? {
fun CPointer<JNIEnvVar>.getString(string: jstring): String {
val chars = getStringChars(string)
return chars?.toKString()
return chars?.toKString() ?: error("Unable to create a String from the given jstring")
}

fun CPointer<JNIEnvVar>.releaseStringChars(string: jstring, chars: CPointer<jcharVar>?) {
Expand Down
25 changes: 15 additions & 10 deletions sample/src/ndkMain/kotlin/io/github/landrynorris/jni/sample/Jni.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ fun getText(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long): jstring {
return repository.getText().toJString(env) ?: error("Unable to create JString")
}

fun crash(env: CPointer<JNIEnvVar>, thiz: jobject, message: jstring) {
val messageString = env.getString(message)
env.fatalError(messageString)
}

@CName("JNI_OnLoad")
fun loadJni(jvm: CPointer<JavaVMVar>, reserved: CPointer<*>): Int {
val env = jvm.env() ?: error("Unable to get JNI environment")
Expand All @@ -59,35 +64,35 @@ fun registerJniNatives(env: CPointer<JNIEnvVar>) {
env.registerNatives {
clazz = env.findClass("io.github.landrynorris.sample.JniBridge".signature())

method {
name = "buttonClicked"
method("buttonClicked") {
signature = signature(::buttonClicked)
function = staticCFunction(::buttonClicked)
}

method {
name = "createRepository"
method("createRepository") {
signature = signature(::createRepository)
function = staticCFunction(::createRepository)
}

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

method {
name = "methodWithParameters"
method("methodWithParameters") {
signature = signature(::methodWithParameters)
function = staticCFunction(::methodWithParameters)
}

method {
name = "callJavaFunction"
method("callJavaFunction") {
signature = signature(::callJavaFunction)
function = staticCFunction(::callJavaFunction)
}

method("crash") {
signature = Signature(listOf(String)).toString()
function = staticCFunction(::crash)
}
}
println("Finished registering native methods")
}

0 comments on commit 3b89167

Please sign in to comment.