// Runtime support for generated JNI bindings: VM/env acquisition, exception // handling, cached class lookup, and string marshalling. // package jni import "core:strings" import "shared:android" Env :: ^android.JNIEnv @(private) vm: ^android.JavaVM // Records the process JavaVM. Call once from android_main before any binding. init :: proc(app: ^android.android_app) -> bool { if app == nil || app.activity == nil { return false } vm = app.activity.vm return vm != nil } // The Activity instance, usable anywhere a Context is expected. activity :: proc(app: ^android.android_app) -> android.jobject { if app == nil || app.activity == nil { return nil } return app.activity.clazz } // The engine's loop runs on a glue-spawned thread, not the one holding // activity.env, so the env has to come from the VM and the thread attached on // first use. The thread lives as long as the app, so it is never detached. @(thread_local) tls_env: Env env :: proc() -> Env { if tls_env != nil { return tls_env } if vm == nil { return nil } e: Env if vm^^.GetEnv(vm, cast(^rawptr)&e, android.JNI_VERSION_1_6) == android.JNI_EDETACHED { if vm^^.AttachCurrentThread(vm, &e, nil) != android.JNI_OK { return nil } } tls_env = e return e } // // Exceptions // // Reports whether a Java exception is pending, printing and clearing it if so. // Left pending, an exception makes every later JNI call on this thread fail. failed :: proc(e: Env) -> bool { if e == nil { return true } if e^^.ExceptionCheck(e) == 0 { return false } e^^.ExceptionDescribe(e) e^^.ExceptionClear(e) return true } // // Lookup // // Resolves a class and pins it with a global ref. Generated code caches the // result in a static, so a class is only ever looked up once. A local ref would // go stale the moment the native frame returns. class_ref :: proc(e: Env, name: cstring) -> android.jclass { local := e^^.FindClass(e, name) if local == nil || failed(e) { return nil } global := e^^.NewGlobalRef(e, android.jobject(local)) e^^.DeleteLocalRef(e, android.jobject(local)) return android.jclass(global) } method_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jmethodID { id := e^^.GetMethodID(e, cls, name, sig) if failed(e) { return nil } return id } static_method_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jmethodID { id := e^^.GetStaticMethodID(e, cls, name, sig) if failed(e) { return nil } return id } field_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jfieldID { id := e^^.GetFieldID(e, cls, name, sig) if failed(e) { return nil } return id } static_field_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jfieldID { id := e^^.GetStaticFieldID(e, cls, name, sig) if failed(e) { return nil } return id } free_local :: proc(e: Env, obj: android.jobject) { if obj != nil { e^^.DeleteLocalRef(e, obj) } } // // Strings // to_jstring :: proc(e: Env, s: string) -> android.jstring { c := strings.clone_to_cstring(s, context.temp_allocator) return e^^.NewStringUTF(e, c) } from_jstring :: proc(e: Env, s: android.jstring, allocator := context.allocator) -> string { if s == nil { return "" } utf := e^^.GetStringUTFChars(e, s, nil) if utf == nil { return "" } out := strings.clone(string(utf), allocator) e^^.ReleaseStringUTFChars(e, s, utf) return out } // // Local reference frames // // Every object returned by a binding is a LOCAL ref, and locals are only // reclaimed when control returns to Java -- which never happens on a native // thread that owns its own loop. A sequence of object-returning calls made // per-frame will therefore exhaust the local ref table (512 entries) and abort // the process. Scope such a sequence in a frame: // // if !jni.push_frame(env) { return false } // defer jni.pop_frame(env) // // Anything still needed after pop_frame must be a primitive, a copied string, // or passed to pop_frame as `keep` to be promoted into the outer frame. // push_frame :: proc(e: Env, capacity: i32 = 16) -> bool { if e == nil { return false } return e^^.PushLocalFrame(e, android.jint(capacity)) == android.JNI_OK } pop_frame :: proc(e: Env, keep: android.jobject = nil) -> android.jobject { if e == nil { return nil } return e^^.PopLocalFrame(e, keep) }