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

Commit 9986312

Browse files
committed
Initial JNI support
1 parent 1f09da7 commit 9986312

File tree

5 files changed

+635
-1437
lines changed

5 files changed

+635
-1437
lines changed

cargo-apk/injected-glue/ffi.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ pub struct JNIInvokeInterface {
11001100
pub DestroyJavaVM: extern fn(*mut JavaVM) -> jint,
11011101
pub AttachCurrentThread: extern fn(*mut JavaVM, *mut *mut JNIEnv, *mut c_void) -> jint,
11021102
pub DetachCurrentThread: extern fn(*mut JavaVM) -> jint,
1103-
pub GetEnv: extern fn(*mut JavaVM, *mut *mut c_void, jint) -> jint,
1103+
pub GetEnv: extern fn(*mut JavaVM, *mut *mut JNIEnv, jint) -> jint,
11041104
pub AttachCurrentThreadAsDaemon: extern fn(*mut JavaVM, *mut *mut JNIEnv, *mut c_void) -> jint,
11051105
}
11061106
pub const JNILocalRefType: i32 = 1;
@@ -1434,3 +1434,8 @@ pub type jstring = *mut class__jstring;
14341434
pub type jthrowable = *mut class__jthrowable;
14351435
pub type jvalue = [u8; 8];
14361436
pub type jweak = *mut class__jobject;
1437+
1438+
pub const JNI_VERSION_1_1 : jint = 0x00010001;
1439+
pub const JNI_VERSION_1_2 : jint = 0x00010002;
1440+
pub const JNI_VERSION_1_4 : jint = 0x00010004;
1441+
pub const JNI_VERSION_1_6 : jint = 0x00010006;

cargo-apk/injected-glue/lib.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use std::slice;
1313
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
1414
use std::io::Write;
1515

16+
macro_rules! cstr { ($s:expr) => (concat!($s, "\0") as *const _ as *const ::std::os::raw::c_char) }
17+
1618
pub type pthread_t = c_long;
1719
pub type pthread_mutexattr_t = c_long;
1820
pub type pthread_attr_t = c_void; // FIXME: wrong
@@ -64,11 +66,39 @@ pub unsafe extern fn cargo_apk_injected_glue_load_asset(ptr: *const (), len: usi
6466
Box::into_raw(Box::new(data)) as *mut _
6567
}
6668

69+
#[no_mangle]
70+
pub unsafe extern fn cargo_apk_injected_glue_jni_attach_thread() -> *mut c_void {
71+
let mut jni_env : *mut ffi::JNIEnv = ptr::null_mut();
72+
let android_app = get_app();
73+
let jvm = (*android_app.activity).vm;
74+
((*(*jvm).functions).AttachCurrentThread)(jvm, &mut jni_env, ptr::null_mut());
75+
jni_env as *mut c_void
76+
}
77+
78+
#[no_mangle]
79+
pub unsafe extern fn cargo_apk_injected_glue_jni_detach_thread() {
80+
let android_app = get_app();
81+
let jvm = (*android_app.activity).vm;
82+
((*(*jvm).functions).DetachCurrentThread)(jvm);
83+
}
84+
85+
#[no_mangle]
86+
pub unsafe extern fn cargo_apk_injected_glue_jni_class_loader() -> *mut c_void {
87+
CLASS_LOADER as *mut c_void
88+
}
89+
90+
#[no_mangle]
91+
pub unsafe extern fn cargo_apk_injected_glue_jni_activity() -> *mut c_void {
92+
ACTIVITY as *mut c_void
93+
}
94+
6795
/// This static variable will store the android_app* on creation, and set it back to 0 at
6896
/// destruction.
6997
/// Apart from this, the static is never written, so there is no risk of race condition.
7098
#[no_mangle]
7199
pub static mut ANDROID_APP: *mut ffi::android_app = 0 as *mut ffi::android_app;
100+
pub static mut CLASS_LOADER: ffi::jobject = 0 as ffi::jobject;
101+
pub static mut ACTIVITY: ffi::jobject = 0 as ffi::jobject;
72102

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

171201
unsafe { ANDROID_APP = app; };
172202
let app: &mut ffi::android_app = unsafe { &mut *app };
173-
203+
204+
// The thread spawned below needs access to the Android class loader
205+
// associated with the main application thread. If not, it would not
206+
// have access to the full set of Android classes.
207+
// See https://developer.android.com/training/articles/perf-jni.html#faq_FindClass
208+
let mut jni_env : *mut ffi::JNIEnv = ptr::null_mut();
209+
let mut class_class : ffi::jclass = ptr::null_mut();
210+
let mut jni_functions : &ffi::JNINativeInterface;
211+
unsafe {
212+
let jvm = (*app.activity).vm;
213+
((*(*jvm).functions).GetEnv)(jvm, &mut jni_env, ffi::JNI_VERSION_1_6);
214+
((*(*jvm).functions).AttachCurrentThread)(jvm, &mut jni_env, ptr::null_mut());
215+
if jni_env.is_null() { panic!("Failed to acquire JNI environment."); }
216+
217+
jni_functions = &*(*jni_env).functions;
218+
class_class = (jni_functions.FindClass)(jni_env, cstr!("java/lang/Class"));
219+
if class_class.is_null() { panic!("Could not find java.lang.Class"); }
220+
let get_classloader_method = (jni_functions.GetMethodID)(jni_env, class_class,
221+
cstr!("getClassLoader"),
222+
cstr!("()Ljava/lang/ClassLoader;"));
223+
if get_classloader_method.is_null() { panic!("Could not find java.lang.Class.GetClassLoader()"); }
224+
225+
let local_class_loader = (jni_functions.CallObjectMethod)(jni_env, class_class, get_classloader_method);
226+
// jobjects accessed from another thread need to be global, for details see
227+
// http://android-developers.blogspot.be/2011/11/jni-local-reference-changes-in-ics.html
228+
CLASS_LOADER = (jni_functions.NewGlobalRef)(jni_env, local_class_loader);
229+
if CLASS_LOADER.is_null() { panic!("Could not obtain classloader instance."); }
230+
// The activity "clazz" field is actually not a class, but the activity instance.
231+
// The documentation lies. This is also why we don't use the clazz field as a class
232+
// to get the classloader above.
233+
ACTIVITY = (jni_functions.NewGlobalRef)(jni_env, (*app.activity).clazz);
234+
if ACTIVITY.is_null() { panic!("Could not obtain activity instance."); }
235+
236+
(jni_functions.DeleteLocalRef)(jni_env, class_class);
237+
(jni_functions.DeleteLocalRef)(jni_env, local_class_loader);
238+
write_log(format!("ClassLoader -> {:X}", CLASS_LOADER as u64).as_str());
239+
write_log(format!("Activity -> {:X}", ACTIVITY as u64).as_str());
240+
if (jni_functions.ExceptionCheck)(jni_env) != 0 { panic!("Exception occurred while looking up ClassLoader and Activity instance."); }
241+
}
242+
174243
// Creating the context that will be passed to the callback
175244
let context = Context {
176245
senders: Mutex::new(Vec::new()),
@@ -342,6 +411,14 @@ pub fn android_main2<F>(app: *mut ffi::android_app, main_function: F)
342411
}
343412
}
344413
}
414+
415+
// TODO: detach JNI thread? Or is this handled elsewhere by default?
416+
unsafe {
417+
(jni_functions.DeleteGlobalRef)(jni_env, CLASS_LOADER);
418+
(jni_functions.DeleteGlobalRef)(jni_env, ACTIVITY);
419+
CLASS_LOADER = 0 as ffi::jobject;
420+
}
421+
345422

346423
// Terminating the application. This kills the thread the Rust main thread.
347424
// TODO: consider waiting on thread?

0 commit comments

Comments
 (0)