Skip to content
This repository was archived by the owner on Jul 3, 2020. It is now read-only.

Initial JNI support #116

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cargo-apk/injected-glue/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ pub struct JNIInvokeInterface {
pub DestroyJavaVM: extern fn(*mut JavaVM) -> jint,
pub AttachCurrentThread: extern fn(*mut JavaVM, *mut *mut JNIEnv, *mut c_void) -> jint,
pub DetachCurrentThread: extern fn(*mut JavaVM) -> jint,
pub GetEnv: extern fn(*mut JavaVM, *mut *mut c_void, jint) -> jint,
pub GetEnv: extern fn(*mut JavaVM, *mut *mut JNIEnv, jint) -> jint,
pub AttachCurrentThreadAsDaemon: extern fn(*mut JavaVM, *mut *mut JNIEnv, *mut c_void) -> jint,
}
pub const JNILocalRefType: i32 = 1;
Expand Down Expand Up @@ -1434,3 +1434,8 @@ pub type jstring = *mut class__jstring;
pub type jthrowable = *mut class__jthrowable;
pub type jvalue = [u8; 8];
pub type jweak = *mut class__jobject;

pub const JNI_VERSION_1_1 : jint = 0x00010001;
pub const JNI_VERSION_1_2 : jint = 0x00010002;
pub const JNI_VERSION_1_4 : jint = 0x00010004;
pub const JNI_VERSION_1_6 : jint = 0x00010006;
79 changes: 78 additions & 1 deletion cargo-apk/injected-glue/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use std::slice;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use std::io::Write;

macro_rules! cstr { ($s:expr) => (concat!($s, "\0") as *const _ as *const ::std::os::raw::c_char) }

pub type pthread_t = c_long;
pub type pthread_mutexattr_t = c_long;
pub type pthread_attr_t = c_void; // FIXME: wrong
Expand Down Expand Up @@ -64,11 +66,39 @@ pub unsafe extern fn cargo_apk_injected_glue_load_asset(ptr: *const (), len: usi
Box::into_raw(Box::new(data)) as *mut _
}

#[no_mangle]
pub unsafe extern fn cargo_apk_injected_glue_jni_attach_thread() -> *mut c_void {
let mut jni_env : *mut ffi::JNIEnv = ptr::null_mut();
let android_app = get_app();
let jvm = (*android_app.activity).vm;
((*(*jvm).functions).AttachCurrentThread)(jvm, &mut jni_env, ptr::null_mut());
jni_env as *mut c_void
}

#[no_mangle]
pub unsafe extern fn cargo_apk_injected_glue_jni_detach_thread() {
let android_app = get_app();
let jvm = (*android_app.activity).vm;
((*(*jvm).functions).DetachCurrentThread)(jvm);
}

#[no_mangle]
pub unsafe extern fn cargo_apk_injected_glue_jni_class_loader() -> *mut c_void {
CLASS_LOADER as *mut c_void
}

#[no_mangle]
pub unsafe extern fn cargo_apk_injected_glue_jni_activity() -> *mut c_void {
ACTIVITY as *mut c_void
}

/// This static variable will store the android_app* on creation, and set it back to 0 at
/// destruction.
/// Apart from this, the static is never written, so there is no risk of race condition.
#[no_mangle]
pub static mut ANDROID_APP: *mut ffi::android_app = 0 as *mut ffi::android_app;
pub static mut CLASS_LOADER: ffi::jobject = 0 as ffi::jobject;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLASS_LOADER and ACTIVITY need some documentation for what they are.

pub static mut ACTIVITY: ffi::jobject = 0 as ffi::jobject;

/// This is the structure that serves as user data in the android_app*
#[doc(hidden)]
Expand Down Expand Up @@ -170,7 +200,46 @@ pub fn android_main2<F>(app: *mut ffi::android_app, main_function: F)

unsafe { ANDROID_APP = app; };
let app: &mut ffi::android_app = unsafe { &mut *app };


// The thread spawned below needs access to the Android class loader
// associated with the main application thread. If not, it would not
// have access to the full set of Android classes.
// See https://developer.android.com/training/articles/perf-jni.html#faq_FindClass
let mut jni_env : *mut ffi::JNIEnv = ptr::null_mut();
let mut class_class : ffi::jclass = ptr::null_mut();
let mut jni_functions : &ffi::JNINativeInterface;
unsafe {
let jvm = (*app.activity).vm;
((*(*jvm).functions).GetEnv)(jvm, &mut jni_env, ffi::JNI_VERSION_1_6);
((*(*jvm).functions).AttachCurrentThread)(jvm, &mut jni_env, ptr::null_mut());
if jni_env.is_null() { panic!("Failed to acquire JNI environment."); }

jni_functions = &*(*jni_env).functions;
class_class = (jni_functions.FindClass)(jni_env, cstr!("java/lang/Class"));
if class_class.is_null() { panic!("Could not find java.lang.Class"); }
let get_classloader_method = (jni_functions.GetMethodID)(jni_env, class_class,
cstr!("getClassLoader"),
cstr!("()Ljava/lang/ClassLoader;"));
if get_classloader_method.is_null() { panic!("Could not find java.lang.Class.GetClassLoader()"); }

let local_class_loader = (jni_functions.CallObjectMethod)(jni_env, class_class, get_classloader_method);
// jobjects accessed from another thread need to be global, for details see
// http://android-developers.blogspot.be/2011/11/jni-local-reference-changes-in-ics.html
CLASS_LOADER = (jni_functions.NewGlobalRef)(jni_env, local_class_loader);
if CLASS_LOADER.is_null() { panic!("Could not obtain classloader instance."); }
// The activity "clazz" field is actually not a class, but the activity instance.
// The documentation lies. This is also why we don't use the clazz field as a class
// to get the classloader above.
ACTIVITY = (jni_functions.NewGlobalRef)(jni_env, (*app.activity).clazz);
if ACTIVITY.is_null() { panic!("Could not obtain activity instance."); }

(jni_functions.DeleteLocalRef)(jni_env, class_class);
(jni_functions.DeleteLocalRef)(jni_env, local_class_loader);
write_log(format!("ClassLoader -> {:X}", CLASS_LOADER as u64).as_str());
write_log(format!("Activity -> {:X}", ACTIVITY as u64).as_str());
if (jni_functions.ExceptionCheck)(jni_env) != 0 { panic!("Exception occurred while looking up ClassLoader and Activity instance."); }
}

// Creating the context that will be passed to the callback
let context = Context {
senders: Mutex::new(Vec::new()),
Expand Down Expand Up @@ -342,6 +411,14 @@ pub fn android_main2<F>(app: *mut ffi::android_app, main_function: F)
}
}
}

// TODO: detach JNI thread? Or is this handled elsewhere by default?
unsafe {
(jni_functions.DeleteGlobalRef)(jni_env, CLASS_LOADER);
(jni_functions.DeleteGlobalRef)(jni_env, ACTIVITY);
CLASS_LOADER = 0 as ffi::jobject;
}


// Terminating the application. This kills the thread the Rust main thread.
// TODO: consider waiting on thread?
Expand Down
Loading