179 lines
4.6 KiB
Odin
179 lines
4.6 KiB
Odin
// 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)
|
|
}
|