mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-20 08:19:55 +00:00
feat(examples): Android example consumes the generated Kotlin/JNI wrapper
Regenerates the wrapper (MyTimerNode.kt) and JNI shim (my_timer_jni.c) via `nimble genbindings_kotlin`, removing the hand-written sources, and points the instrumented test at the derived MyTimerNode class. Validated by cross-compiling both ABIs with the NDK: arm64-v8a + x86_64 build clean, the four Java_org_logos_mytimer_MyTimerNode_* symbols are exported, and libmy_timer_jni.so correctly NEEDS libmy_timer.so. (The Kotlin/Gradle layer still runs on a device/emulator.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e36fe62033
commit
d554443121
@ -1,9 +1,10 @@
|
||||
// JNI bridge: exposes the timer library's native C ABI to Kotlin/Java.
|
||||
// Generated by nim-ffi Kotlin/JNI codegen. Do not edit by hand.
|
||||
//
|
||||
// JNI bridge exposing the library's native (zero-serialization) C ABI to Kotlin.
|
||||
// The library calls back on its own FFI thread; each bridge function blocks on a
|
||||
// condvar until the callback fires, then returns a plain Java value — so the
|
||||
// Kotlin side sees a simple synchronous API. A struct return (EchoResponse) is
|
||||
// read out of the typed C-POD inside the callback (valid only there).
|
||||
// Kotlin side sees a simple synchronous API. A struct return is read out of the
|
||||
// typed C-POD inside the callback (valid only there).
|
||||
#include "my_timer.h"
|
||||
|
||||
#include <jni.h>
|
||||
@ -13,9 +14,8 @@
|
||||
|
||||
typedef struct {
|
||||
int ret, done;
|
||||
char text[1024]; // string return / error text
|
||||
char echoed[256]; // EchoResponse.echoed
|
||||
char timerName[256];
|
||||
char text[1024]; // string return / error text
|
||||
char fields[2][256]; // string fields of a struct return
|
||||
pthread_mutex_t mu;
|
||||
pthread_cond_t cv;
|
||||
} Resp;
|
||||
@ -59,14 +59,15 @@ static void string_cb(int ret, const char *msg, size_t len, void *ud) {
|
||||
pthread_cond_signal(&r->cv);
|
||||
pthread_mutex_unlock(&r->mu);
|
||||
}
|
||||
|
||||
static void echo_cb(int ret, const char *msg, size_t len, void *ud) {
|
||||
Resp *r = ud;
|
||||
pthread_mutex_lock(&r->mu);
|
||||
r->ret = ret;
|
||||
if (ret == RET_OK) {
|
||||
const EchoResponse *e = (const EchoResponse *)msg; // typed struct return
|
||||
strncpy(r->echoed, e->echoed, sizeof(r->echoed) - 1);
|
||||
strncpy(r->timerName, e->timerName, sizeof(r->timerName) - 1);
|
||||
const EchoResponse *e = (const EchoResponse *)msg;
|
||||
strncpy(r->fields[0], e->echoed, sizeof(r->fields[0]) - 1);
|
||||
strncpy(r->fields[1], e->timerName, sizeof(r->fields[1]) - 1);
|
||||
} else {
|
||||
copy_raw(r->text, sizeof(r->text), msg, len);
|
||||
}
|
||||
@ -76,23 +77,39 @@ static void echo_cb(int ret, const char *msg, size_t len, void *ud) {
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_logos_mytimer_TimerNode_nativeCreate(JNIEnv *env, jobject thiz,
|
||||
jstring jname) {
|
||||
const char *name = (*env)->GetStringUTFChars(env, jname, NULL);
|
||||
Java_org_logos_mytimer_MyTimerNode_nativeCreate(JNIEnv *env, jobject thiz, jstring j_name) {
|
||||
const char *name = (*env)->GetStringUTFChars(env, j_name, NULL);
|
||||
TimerConfig config = {.name = name};
|
||||
Resp r;
|
||||
resp_init(&r);
|
||||
TimerConfig cfg = {.name = name};
|
||||
void *ctx = my_timer_create(cfg, ack_cb, &r);
|
||||
void *ctx = my_timer_create(config, ack_cb, &r);
|
||||
resp_wait(&r);
|
||||
(*env)->ReleaseStringUTFChars(env, jname, name);
|
||||
(*env)->ReleaseStringUTFChars(env, j_name, name);
|
||||
resp_destroy(&r);
|
||||
(void)thiz;
|
||||
return (jlong)(intptr_t)ctx;
|
||||
}
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_logos_mytimer_MyTimerNode_nativeEcho(JNIEnv *env, jobject thiz, jlong ctx, jstring j_message, jlong delayMs) {
|
||||
const char *message = (*env)->GetStringUTFChars(env, j_message, NULL);
|
||||
EchoRequest req = {.message = message, .delayMs = (int64_t)delayMs};
|
||||
Resp r;
|
||||
resp_init(&r);
|
||||
if (my_timer_echo((void *)(intptr_t)ctx, echo_cb, &r, req) == RET_OK)
|
||||
resp_wait(&r);
|
||||
(*env)->ReleaseStringUTFChars(env, j_message, message);
|
||||
jclass strClass = (*env)->FindClass(env, "java/lang/String");
|
||||
jobjectArray arr = (*env)->NewObjectArray(env, 2, strClass, NULL);
|
||||
(*env)->SetObjectArrayElement(env, arr, 0, (*env)->NewStringUTF(env, r.fields[0]));
|
||||
(*env)->SetObjectArrayElement(env, arr, 1, (*env)->NewStringUTF(env, r.fields[1]));
|
||||
resp_destroy(&r);
|
||||
(void)thiz;
|
||||
return arr;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_logos_mytimer_TimerNode_nativeVersion(JNIEnv *env, jobject thiz,
|
||||
jlong ctx) {
|
||||
Java_org_logos_mytimer_MyTimerNode_nativeVersion(JNIEnv *env, jobject thiz, jlong ctx) {
|
||||
Resp r;
|
||||
resp_init(&r);
|
||||
if (my_timer_version((void *)(intptr_t)ctx, string_cb, &r) == RET_OK)
|
||||
@ -103,30 +120,8 @@ Java_org_logos_mytimer_TimerNode_nativeVersion(JNIEnv *env, jobject thiz,
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns String[2] = { echoed, timerName }.
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_logos_mytimer_TimerNode_nativeEcho(JNIEnv *env, jobject thiz, jlong ctx,
|
||||
jstring jmsg, jlong delayMs) {
|
||||
const char *msg = (*env)->GetStringUTFChars(env, jmsg, NULL);
|
||||
Resp r;
|
||||
resp_init(&r);
|
||||
EchoRequest req = {.message = msg, .delayMs = (int64_t)delayMs};
|
||||
if (my_timer_echo((void *)(intptr_t)ctx, echo_cb, &r, req) == RET_OK)
|
||||
resp_wait(&r);
|
||||
(*env)->ReleaseStringUTFChars(env, jmsg, msg);
|
||||
|
||||
jclass strClass = (*env)->FindClass(env, "java/lang/String");
|
||||
jobjectArray arr = (*env)->NewObjectArray(env, 2, strClass, NULL);
|
||||
(*env)->SetObjectArrayElement(env, arr, 0, (*env)->NewStringUTF(env, r.echoed));
|
||||
(*env)->SetObjectArrayElement(env, arr, 1, (*env)->NewStringUTF(env, r.timerName));
|
||||
resp_destroy(&r);
|
||||
(void)thiz;
|
||||
return arr;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_logos_mytimer_TimerNode_nativeDestroy(JNIEnv *env, jobject thiz,
|
||||
jlong ctx) {
|
||||
Java_org_logos_mytimer_MyTimerNode_nativeDestroy(JNIEnv *env, jobject thiz, jlong ctx) {
|
||||
my_timer_destroy((void *)(intptr_t)ctx);
|
||||
(void)env;
|
||||
(void)thiz;
|
||||
|
||||
@ -11,7 +11,7 @@ import org.junit.runner.RunWith
|
||||
class TimerNodeTest {
|
||||
@Test
|
||||
fun createVersionEcho() {
|
||||
TimerNode("android-demo").use { node ->
|
||||
MyTimerNode("android-demo").use { node ->
|
||||
assertEquals("nim-timer v0.1.0", node.version())
|
||||
val r = node.echo("hello from Kotlin", delayMs = 2)
|
||||
assertEquals("hello from Kotlin", r.echoed)
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
// Generated by nim-ffi Kotlin codegen. Do not edit by hand.
|
||||
package org.logos.mytimer
|
||||
|
||||
/** Typed result mirroring the library's EchoResponse. */
|
||||
data class EchoResponse(val echoed: String, val timerName: String)
|
||||
|
||||
class MyTimerNode(name: String) : AutoCloseable {
|
||||
private val ctx: Long = nativeCreate(name)
|
||||
|
||||
fun echo(message: String, delayMs: Long = 0): EchoResponse {
|
||||
val a = nativeEcho(ctx, message, delayMs)
|
||||
return EchoResponse(a[0], a[1])
|
||||
}
|
||||
fun version(): String = nativeVersion(ctx)
|
||||
override fun close() = nativeDestroy(ctx)
|
||||
|
||||
private external fun nativeCreate(name: String): Long
|
||||
private external fun nativeEcho(ctx: Long, message: String, delayMs: Long): Array<String>
|
||||
private external fun nativeVersion(ctx: Long): String
|
||||
private external fun nativeDestroy(ctx: Long)
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("my_timer")
|
||||
System.loadLibrary("my_timer_jni")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package org.logos.mytimer
|
||||
|
||||
/** Typed result of [TimerNode.echo] — mirrors the library's EchoResponse. */
|
||||
data class EchoResult(val echoed: String, val timerName: String)
|
||||
|
||||
/**
|
||||
* Idiomatic Kotlin wrapper over the timer library's native C ABI, bridged through
|
||||
* JNI (see jni/my_timer_jni.c). Each call blocks until the library's FFI-thread
|
||||
* callback fires, so these methods are simple and synchronous.
|
||||
*
|
||||
* Call [close] (or use Kotlin's `use { }`) to tear the context down.
|
||||
*/
|
||||
class TimerNode(name: String) : AutoCloseable {
|
||||
private val ctx: Long = nativeCreate(name)
|
||||
|
||||
fun version(): String = nativeVersion(ctx)
|
||||
|
||||
fun echo(message: String, delayMs: Long = 0): EchoResult {
|
||||
val a = nativeEcho(ctx, message, delayMs)
|
||||
return EchoResult(a[0], a[1])
|
||||
}
|
||||
|
||||
override fun close() = nativeDestroy(ctx)
|
||||
|
||||
private external fun nativeCreate(name: String): Long
|
||||
private external fun nativeVersion(ctx: Long): String
|
||||
private external fun nativeEcho(ctx: Long, message: String, delayMs: Long): Array<String>
|
||||
private external fun nativeDestroy(ctx: Long)
|
||||
|
||||
companion object {
|
||||
init {
|
||||
// libmy_timer.so (the Nim library) is a NEEDED dependency of the JNI
|
||||
// shim, but load it explicitly first so the linker finds it.
|
||||
System.loadLibrary("my_timer")
|
||||
System.loadLibrary("my_timer_jni")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user