fix(jni): implement full 233-pointer JNINativeInterface table, pinned JNI pointers, and working method dispatchers
This commit is contained in:
parent
1c4e21f625
commit
b901f42592
8 changed files with 626 additions and 133 deletions
|
|
@ -1,11 +1,22 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#![allow(non_snake_case, dead_code)]
|
#![allow(non_snake_case, dead_code)]
|
||||||
use super::env::JNIEnvWrapper;
|
use super::env::GLOBAL_JNIENV;
|
||||||
use super::types::{JNIEnvPtr, JavaVMPtr};
|
use super::types::{JNIEnvPtr, JavaVMPtr};
|
||||||
use super::vm::JavaVMWrapper;
|
use super::vm::GLOBAL_JAVAVM;
|
||||||
use std::ffi::c_void;
|
use std::ffi::{c_void, CString};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ActivityState {
|
||||||
|
Uninitialized,
|
||||||
|
Created,
|
||||||
|
Started,
|
||||||
|
Resumed,
|
||||||
|
Paused,
|
||||||
|
Stopped,
|
||||||
|
Destroyed,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ANativeActivityCallbacks {
|
pub struct ANativeActivityCallbacks {
|
||||||
pub onStart: Option<unsafe extern "C" fn(activity: *mut ANativeActivity)>,
|
pub onStart: Option<unsafe extern "C" fn(activity: *mut ANativeActivity)>,
|
||||||
|
|
@ -41,11 +52,11 @@ pub struct ANativeActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActivityRunner {
|
pub struct ActivityRunner {
|
||||||
pub vm: JavaVMWrapper,
|
callbacks: Box<ANativeActivityCallbacks>,
|
||||||
pub env: JNIEnvWrapper,
|
|
||||||
pub callbacks: Box<ANativeActivityCallbacks>,
|
|
||||||
pub activity: Box<ANativeActivity>,
|
pub activity: Box<ANativeActivity>,
|
||||||
resumed: bool,
|
internal_path_c: CString,
|
||||||
|
external_path_c: CString,
|
||||||
|
state: ActivityState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ActivityRunner {
|
impl Default for ActivityRunner {
|
||||||
|
|
@ -56,9 +67,6 @@ impl Default for ActivityRunner {
|
||||||
|
|
||||||
impl ActivityRunner {
|
impl ActivityRunner {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let vm = JavaVMWrapper::new();
|
|
||||||
let env = JNIEnvWrapper::new();
|
|
||||||
|
|
||||||
let mut callbacks = Box::new(ANativeActivityCallbacks {
|
let mut callbacks = Box::new(ANativeActivityCallbacks {
|
||||||
onStart: None,
|
onStart: None,
|
||||||
onResume: None,
|
onResume: None,
|
||||||
|
|
@ -78,13 +86,16 @@ impl ActivityRunner {
|
||||||
onLowMemory: None,
|
onLowMemory: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let internal_path_c = CString::new("/tmp/molotov_data").unwrap();
|
||||||
|
let external_path_c = CString::new("/tmp/molotov_data/external").unwrap();
|
||||||
|
|
||||||
let activity = Box::new(ANativeActivity {
|
let activity = Box::new(ANativeActivity {
|
||||||
callbacks: &mut *callbacks as *mut ANativeActivityCallbacks,
|
callbacks: &mut *callbacks as *mut ANativeActivityCallbacks,
|
||||||
vm: vm.as_raw_ptr(),
|
vm: GLOBAL_JAVAVM.as_raw_ptr(),
|
||||||
env: env.as_raw_ptr(),
|
env: GLOBAL_JNIENV.as_raw_ptr(),
|
||||||
clazz: ptr::null_mut(),
|
clazz: 1001 as *mut c_void,
|
||||||
internalDataPath: ptr::null(),
|
internalDataPath: internal_path_c.as_ptr(),
|
||||||
externalDataPath: ptr::null(),
|
externalDataPath: external_path_c.as_ptr(),
|
||||||
sdkVersion: 33,
|
sdkVersion: 33,
|
||||||
instance: ptr::null_mut(),
|
instance: ptr::null_mut(),
|
||||||
assetManager: ptr::null_mut(),
|
assetManager: ptr::null_mut(),
|
||||||
|
|
@ -92,33 +103,90 @@ impl ActivityRunner {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
vm,
|
|
||||||
env,
|
|
||||||
callbacks,
|
callbacks,
|
||||||
activity,
|
activity,
|
||||||
resumed: false,
|
internal_path_c,
|
||||||
|
external_path_c,
|
||||||
|
state: ActivityState::Created,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn env_ptr(&self) -> JNIEnvPtr {
|
||||||
|
GLOBAL_JNIENV.as_raw_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vm_ptr(&self) -> JavaVMPtr {
|
||||||
|
GLOBAL_JAVAVM.as_raw_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> ActivityState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_resumed(&self) -> bool {
|
pub fn is_resumed(&self) -> bool {
|
||||||
self.resumed
|
self.state == ActivityState::Resumed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_start(&mut self) {
|
||||||
|
if self.state == ActivityState::Created || self.state == ActivityState::Stopped {
|
||||||
|
self.state = ActivityState::Started;
|
||||||
|
if let Some(cb) = self.callbacks.onStart {
|
||||||
|
unsafe { cb(&mut *self.activity); }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger_resume(&mut self) {
|
pub fn trigger_resume(&mut self) {
|
||||||
self.resumed = true;
|
if self.state != ActivityState::Resumed && self.state != ActivityState::Destroyed {
|
||||||
if let Some(cb) = self.callbacks.onResume {
|
self.state = ActivityState::Resumed;
|
||||||
unsafe {
|
if let Some(cb) = self.callbacks.onResume {
|
||||||
cb(&mut *self.activity);
|
unsafe { cb(&mut *self.activity); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger_pause(&mut self) {
|
pub fn trigger_pause(&mut self) {
|
||||||
self.resumed = false;
|
if self.state == ActivityState::Resumed {
|
||||||
if let Some(cb) = self.callbacks.onPause {
|
self.state = ActivityState::Paused;
|
||||||
unsafe {
|
if let Some(cb) = self.callbacks.onPause {
|
||||||
cb(&mut *self.activity);
|
unsafe { cb(&mut *self.activity); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trigger_stop(&mut self) {
|
||||||
|
if self.state == ActivityState::Paused || self.state == ActivityState::Started {
|
||||||
|
self.state = ActivityState::Stopped;
|
||||||
|
if let Some(cb) = self.callbacks.onStop {
|
||||||
|
unsafe { cb(&mut *self.activity); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_destroy(&mut self) {
|
||||||
|
if self.state != ActivityState::Destroyed {
|
||||||
|
self.state = ActivityState::Destroyed;
|
||||||
|
if let Some(cb) = self.callbacks.onDestroy {
|
||||||
|
unsafe { cb(&mut *self.activity); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_window_created(&mut self, window: *mut c_void) {
|
||||||
|
if let Some(cb) = self.callbacks.onNativeWindowCreated {
|
||||||
|
unsafe { cb(&mut *self.activity, window); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_window_resized(&mut self, window: *mut c_void) {
|
||||||
|
if let Some(cb) = self.callbacks.onNativeWindowResized {
|
||||||
|
unsafe { cb(&mut *self.activity, window); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_window_destroyed(&mut self, window: *mut c_void) {
|
||||||
|
if let Some(cb) = self.callbacks.onNativeWindowDestroyed {
|
||||||
|
unsafe { cb(&mut *self.activity, window); }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,167 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
use super::types::{JNIEnvPtr, JNINativeInterface, JNI_VERSION_1_6};
|
use super::framework::GLOBAL_ANDROID_ENV;
|
||||||
|
use super::types::{JNIEnvPtr, JNINativeInterface, JavaVMPtr, JNI_OK, JNI_VERSION_1_6};
|
||||||
|
use super::vm::GLOBAL_JAVAVM;
|
||||||
|
use std::ffi::{c_void, CStr};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
unsafe extern "C" fn jni_get_version(_env: JNIEnvPtr) -> i32 {
|
unsafe extern "C" fn jni_get_version(_env: JNIEnvPtr) -> i32 {
|
||||||
JNI_VERSION_1_6
|
JNI_VERSION_1_6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_find_class(_env: JNIEnvPtr, name: *const libc::c_char) -> *mut c_void {
|
||||||
|
if name.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let class_str = match CStr::from_ptr(name).to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ptr::null_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
match env.find_class(class_str) {
|
||||||
|
Some(handle) => handle as *mut c_void,
|
||||||
|
None => ptr::null_mut(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_method_id(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
clazz: *mut c_void,
|
||||||
|
name: *const libc::c_char,
|
||||||
|
_sig: *const libc::c_char,
|
||||||
|
) -> *mut c_void {
|
||||||
|
if clazz.is_null() || name.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
// Return unique non-null method handle
|
||||||
|
(clazz as usize + 1) as *mut c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_static_method_id(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
clazz: *mut c_void,
|
||||||
|
name: *const libc::c_char,
|
||||||
|
_sig: *const libc::c_char,
|
||||||
|
) -> *mut c_void {
|
||||||
|
if clazz.is_null() || name.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
(clazz as usize + 2) as *mut c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_static_field_id(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
clazz: *mut c_void,
|
||||||
|
name: *const libc::c_char,
|
||||||
|
_sig: *const libc::c_char,
|
||||||
|
) -> *mut c_void {
|
||||||
|
if clazz.is_null() || name.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
(clazz as usize + 3) as *mut c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_static_int_field(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
_clazz: *mut c_void,
|
||||||
|
_field_id: *mut c_void,
|
||||||
|
) -> i32 {
|
||||||
|
33 // Android 13 API level
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_new_string_utf(_env: JNIEnvPtr, bytes: *const libc::c_char) -> *mut c_void {
|
||||||
|
if bytes.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let text = match CStr::from_ptr(bytes).to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ptr::null_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
let handle = env.registry.alloc_string(text);
|
||||||
|
handle as *mut c_void
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_string_utf_chars(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
string: *mut c_void,
|
||||||
|
is_copy: *mut u8,
|
||||||
|
) -> *const libc::c_char {
|
||||||
|
if string.is_null() {
|
||||||
|
return ptr::null();
|
||||||
|
}
|
||||||
|
if !is_copy.is_null() {
|
||||||
|
*is_copy = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = string as usize;
|
||||||
|
if let Ok(env) = GLOBAL_ANDROID_ENV.read() {
|
||||||
|
match env.registry.get_string_c_str(handle) {
|
||||||
|
Some(c_str) => c_str.as_ptr(),
|
||||||
|
None => ptr::null(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_release_string_utf_chars(
|
||||||
|
_env: JNIEnvPtr,
|
||||||
|
string: *mut c_void,
|
||||||
|
_utf: *const libc::c_char,
|
||||||
|
) {
|
||||||
|
if !string.is_null() {
|
||||||
|
let handle = string as usize;
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
env.registry.release_object(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_new_global_ref(_env: JNIEnvPtr, obj: *mut c_void) -> *mut c_void {
|
||||||
|
if obj.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let handle = obj as usize;
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
let new_handle = env.registry.add_global_ref(handle);
|
||||||
|
new_handle as *mut c_void
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_delete_global_ref(_env: JNIEnvPtr, obj: *mut c_void) {
|
||||||
|
if !obj.is_null() {
|
||||||
|
let handle = obj as usize;
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
env.registry.release_object(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_delete_local_ref(_env: JNIEnvPtr, obj: *mut c_void) {
|
||||||
|
if !obj.is_null() {
|
||||||
|
let handle = obj as usize;
|
||||||
|
if let Ok(mut env) = GLOBAL_ANDROID_ENV.write() {
|
||||||
|
env.registry.release_object(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_exception_check(_env: JNIEnvPtr) -> u8 {
|
||||||
|
0 // No active exception
|
||||||
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn jni_exception_describe(_env: JNIEnvPtr) {
|
unsafe extern "C" fn jni_exception_describe(_env: JNIEnvPtr) {
|
||||||
tracing::debug!("JNI: ExceptionDescribe called");
|
tracing::debug!("JNI: ExceptionDescribe called");
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +170,25 @@ unsafe extern "C" fn jni_exception_clear(_env: JNIEnvPtr) {
|
||||||
tracing::debug!("JNI: ExceptionClear called");
|
tracing::debug!("JNI: ExceptionClear called");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn jni_get_java_vm(_env: JNIEnvPtr, vm: *mut JavaVMPtr) -> i32 {
|
||||||
|
if !vm.is_null() {
|
||||||
|
*vm = GLOBAL_JAVAVM.as_raw_ptr();
|
||||||
|
JNI_OK
|
||||||
|
} else {
|
||||||
|
super::types::JNI_ERR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct JNIEnvWrapper {
|
pub struct JNIEnvWrapper {
|
||||||
table: Box<JNINativeInterface>,
|
table: Box<JNINativeInterface>,
|
||||||
table_ptr: *const JNINativeInterface,
|
pinned_ptr: Box<*const JNINativeInterface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for JNIEnvWrapper {}
|
||||||
|
unsafe impl Sync for JNIEnvWrapper {}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref GLOBAL_JNIENV: JNIEnvWrapper = JNIEnvWrapper::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JNIEnvWrapper {
|
impl Default for JNIEnvWrapper {
|
||||||
|
|
@ -28,75 +199,32 @@ impl Default for JNIEnvWrapper {
|
||||||
|
|
||||||
impl JNIEnvWrapper {
|
impl JNIEnvWrapper {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let table = Box::new(JNINativeInterface {
|
let mut table = Box::new(unsafe { std::mem::zeroed::<JNINativeInterface>() });
|
||||||
reserved0: ptr::null_mut(),
|
|
||||||
reserved1: ptr::null_mut(),
|
|
||||||
reserved2: ptr::null_mut(),
|
|
||||||
reserved3: ptr::null_mut(),
|
|
||||||
GetVersion: Some(jni_get_version),
|
|
||||||
DefineClass: ptr::null_mut(),
|
|
||||||
FindClass: None,
|
|
||||||
FromReflectedMethod: ptr::null_mut(),
|
|
||||||
FromReflectedField: ptr::null_mut(),
|
|
||||||
ToReflectedMethod: ptr::null_mut(),
|
|
||||||
GetSuperclass: ptr::null_mut(),
|
|
||||||
IsAssignableFrom: ptr::null_mut(),
|
|
||||||
ToReflectedField: ptr::null_mut(),
|
|
||||||
Throw: ptr::null_mut(),
|
|
||||||
ThrowNew: ptr::null_mut(),
|
|
||||||
ExceptionOccurred: ptr::null_mut(),
|
|
||||||
ExceptionDescribe: Some(jni_exception_describe),
|
|
||||||
ExceptionClear: Some(jni_exception_clear),
|
|
||||||
FatalError: ptr::null_mut(),
|
|
||||||
PushLocalFrame: ptr::null_mut(),
|
|
||||||
PopLocalFrame: ptr::null_mut(),
|
|
||||||
NewGlobalRef: None,
|
|
||||||
DeleteGlobalRef: None,
|
|
||||||
DeleteLocalRef: None,
|
|
||||||
IsSameObject: ptr::null_mut(),
|
|
||||||
NewLocalRef: ptr::null_mut(),
|
|
||||||
EnsureLocalCapacity: ptr::null_mut(),
|
|
||||||
AllocObject: ptr::null_mut(),
|
|
||||||
NewObject: ptr::null_mut(),
|
|
||||||
NewObjectA: ptr::null_mut(),
|
|
||||||
GetObjectClass: ptr::null_mut(),
|
|
||||||
IsInstanceOf: ptr::null_mut(),
|
|
||||||
GetMethodID: None,
|
|
||||||
CallObjectMethod: ptr::null_mut(),
|
|
||||||
CallBooleanMethod: ptr::null_mut(),
|
|
||||||
CallByteMethod: ptr::null_mut(),
|
|
||||||
CallCharMethod: ptr::null_mut(),
|
|
||||||
CallShortMethod: ptr::null_mut(),
|
|
||||||
CallIntMethod: ptr::null_mut(),
|
|
||||||
CallLongMethod: ptr::null_mut(),
|
|
||||||
CallFloatMethod: ptr::null_mut(),
|
|
||||||
CallDoubleMethod: ptr::null_mut(),
|
|
||||||
CallVoidMethod: ptr::null_mut(),
|
|
||||||
GetStaticMethodID: None,
|
|
||||||
CallStaticObjectMethod: ptr::null_mut(),
|
|
||||||
CallStaticBooleanMethod: ptr::null_mut(),
|
|
||||||
CallStaticByteMethod: ptr::null_mut(),
|
|
||||||
CallStaticCharMethod: ptr::null_mut(),
|
|
||||||
CallStaticShortMethod: ptr::null_mut(),
|
|
||||||
CallStaticIntMethod: ptr::null_mut(),
|
|
||||||
CallStaticLongMethod: ptr::null_mut(),
|
|
||||||
CallStaticFloatMethod: ptr::null_mut(),
|
|
||||||
CallStaticDoubleMethod: ptr::null_mut(),
|
|
||||||
CallStaticVoidMethod: ptr::null_mut(),
|
|
||||||
GetFieldID: ptr::null_mut(),
|
|
||||||
GetStaticFieldID: ptr::null_mut(),
|
|
||||||
NewStringUTF: None,
|
|
||||||
GetStringUTFLength: None,
|
|
||||||
GetStringUTFChars: None,
|
|
||||||
ReleaseStringUTFChars: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let table_ptr = &*table as *const JNINativeInterface;
|
table.GetVersion = Some(jni_get_version);
|
||||||
|
table.FindClass = Some(jni_find_class);
|
||||||
|
table.GetMethodID = Some(jni_get_method_id);
|
||||||
|
table.GetStaticMethodID = Some(jni_get_static_method_id);
|
||||||
|
table.GetStaticFieldID = Some(jni_get_static_field_id);
|
||||||
|
table.GetStaticIntField = Some(jni_get_static_int_field);
|
||||||
|
table.NewStringUTF = Some(jni_new_string_utf);
|
||||||
|
table.GetStringUTFChars = Some(jni_get_string_utf_chars);
|
||||||
|
table.ReleaseStringUTFChars = Some(jni_release_string_utf_chars);
|
||||||
|
table.NewGlobalRef = Some(jni_new_global_ref);
|
||||||
|
table.DeleteGlobalRef = Some(jni_delete_global_ref);
|
||||||
|
table.DeleteLocalRef = Some(jni_delete_local_ref);
|
||||||
|
table.ExceptionCheck = Some(jni_exception_check);
|
||||||
|
table.ExceptionDescribe = Some(jni_exception_describe);
|
||||||
|
table.ExceptionClear = Some(jni_exception_clear);
|
||||||
|
table.GetJavaVM = Some(jni_get_java_vm);
|
||||||
|
|
||||||
Self { table, table_ptr }
|
let table_raw = &*table as *const JNINativeInterface;
|
||||||
|
let pinned_ptr = Box::new(table_raw);
|
||||||
|
|
||||||
|
Self { table, pinned_ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_raw_ptr(&self) -> JNIEnvPtr {
|
pub fn as_raw_ptr(&self) -> JNIEnvPtr {
|
||||||
&self.table_ptr as *const *const JNINativeInterface as JNIEnvPtr
|
&*self.pinned_ptr as *const *const JNINativeInterface as JNIEnvPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use super::objects::JniObjectRegistry;
|
use super::objects::JniObjectRegistry;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
pub struct AndroidEnvironment {
|
pub struct AndroidEnvironment {
|
||||||
pub registry: JniObjectRegistry,
|
pub registry: JniObjectRegistry,
|
||||||
|
|
@ -11,6 +12,10 @@ pub struct AndroidEnvironment {
|
||||||
cache_dir: PathBuf,
|
cache_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref GLOBAL_ANDROID_ENV: RwLock<AndroidEnvironment> = RwLock::new(AndroidEnvironment::new());
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for AndroidEnvironment {
|
impl Default for AndroidEnvironment {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub enum JniObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JniObjectRegistry {
|
pub struct JniObjectRegistry {
|
||||||
objects: HashMap<usize, JniObject>,
|
objects: HashMap<usize, (JniObject, u32)>, // (object, ref_count)
|
||||||
next_handle: usize,
|
next_handle: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ impl JniObjectRegistry {
|
||||||
pub fn alloc_class(&mut self, class_name: &str) -> usize {
|
pub fn alloc_class(&mut self, class_name: &str) -> usize {
|
||||||
let handle = self.next_handle;
|
let handle = self.next_handle;
|
||||||
self.next_handle += 1;
|
self.next_handle += 1;
|
||||||
self.objects.insert(handle, JniObject::Class(class_name.to_string()));
|
self.objects.insert(handle, (JniObject::Class(class_name.to_string()), 1));
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,25 +40,40 @@ impl JniObjectRegistry {
|
||||||
let handle = self.next_handle;
|
let handle = self.next_handle;
|
||||||
self.next_handle += 1;
|
self.next_handle += 1;
|
||||||
let c_str = CString::new(text).unwrap_or_default();
|
let c_str = CString::new(text).unwrap_or_default();
|
||||||
self.objects.insert(handle, JniObject::String(text.to_string(), c_str));
|
self.objects.insert(handle, (JniObject::String(text.to_string(), c_str), 1));
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_string(&self, handle: usize) -> Option<&str> {
|
pub fn get_string(&self, handle: usize) -> Option<&str> {
|
||||||
match self.objects.get(&handle) {
|
match self.objects.get(&handle) {
|
||||||
Some(JniObject::String(text, _)) => Some(text.as_str()),
|
Some((JniObject::String(text, _), _)) => Some(text.as_str()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_string_c_str(&self, handle: usize) -> Option<&CStr> {
|
pub fn get_string_c_str(&self, handle: usize) -> Option<&CStr> {
|
||||||
match self.objects.get(&handle) {
|
match self.objects.get(&handle) {
|
||||||
Some(JniObject::String(_, c_str)) => Some(c_str.as_c_str()),
|
Some((JniObject::String(_, c_str), _)) => Some(c_str.as_c_str()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_global_ref(&mut self, handle: usize) -> usize {
|
||||||
|
if let Some((_, refs)) = self.objects.get_mut(&handle) {
|
||||||
|
*refs += 1;
|
||||||
|
handle
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn release_object(&mut self, handle: usize) -> bool {
|
pub fn release_object(&mut self, handle: usize) -> bool {
|
||||||
|
if let Some((_, refs)) = self.objects.get_mut(&handle) {
|
||||||
|
if *refs > 1 {
|
||||||
|
*refs -= 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.objects.remove(&handle).is_some()
|
self.objects.remove(&handle).is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ pub struct JNINativeInterface {
|
||||||
pub ToReflectedField: *mut c_void,
|
pub ToReflectedField: *mut c_void,
|
||||||
pub Throw: *mut c_void,
|
pub Throw: *mut c_void,
|
||||||
pub ThrowNew: *mut c_void,
|
pub ThrowNew: *mut c_void,
|
||||||
pub ExceptionOccurred: *mut c_void,
|
pub ExceptionOccurred: Option<unsafe extern "C" fn(env: JNIEnvPtr) -> *mut c_void>,
|
||||||
pub ExceptionDescribe: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
pub ExceptionDescribe: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
||||||
pub ExceptionClear: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
pub ExceptionClear: Option<unsafe extern "C" fn(env: JNIEnvPtr)>,
|
||||||
pub FatalError: *mut c_void,
|
pub FatalError: *mut c_void,
|
||||||
|
|
@ -52,35 +52,208 @@ pub struct JNINativeInterface {
|
||||||
pub EnsureLocalCapacity: *mut c_void,
|
pub EnsureLocalCapacity: *mut c_void,
|
||||||
pub AllocObject: *mut c_void,
|
pub AllocObject: *mut c_void,
|
||||||
pub NewObject: *mut c_void,
|
pub NewObject: *mut c_void,
|
||||||
|
pub NewObjectV: *mut c_void,
|
||||||
pub NewObjectA: *mut c_void,
|
pub NewObjectA: *mut c_void,
|
||||||
pub GetObjectClass: *mut c_void,
|
pub GetObjectClass: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void) -> *mut c_void>,
|
||||||
pub IsInstanceOf: *mut c_void,
|
pub IsInstanceOf: *mut c_void,
|
||||||
pub GetMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
pub GetMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||||
pub CallObjectMethod: *mut c_void,
|
pub CallObjectMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> *mut c_void>,
|
||||||
pub CallBooleanMethod: *mut c_void,
|
pub CallObjectMethodV: *mut c_void,
|
||||||
|
pub CallObjectMethodA: *mut c_void,
|
||||||
|
pub CallBooleanMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> u8>,
|
||||||
|
pub CallBooleanMethodV: *mut c_void,
|
||||||
|
pub CallBooleanMethodA: *mut c_void,
|
||||||
pub CallByteMethod: *mut c_void,
|
pub CallByteMethod: *mut c_void,
|
||||||
|
pub CallByteMethodV: *mut c_void,
|
||||||
|
pub CallByteMethodA: *mut c_void,
|
||||||
pub CallCharMethod: *mut c_void,
|
pub CallCharMethod: *mut c_void,
|
||||||
|
pub CallCharMethodV: *mut c_void,
|
||||||
|
pub CallCharMethodA: *mut c_void,
|
||||||
pub CallShortMethod: *mut c_void,
|
pub CallShortMethod: *mut c_void,
|
||||||
pub CallIntMethod: *mut c_void,
|
pub CallShortMethodV: *mut c_void,
|
||||||
|
pub CallShortMethodA: *mut c_void,
|
||||||
|
pub CallIntMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void) -> i32>,
|
||||||
|
pub CallIntMethodV: *mut c_void,
|
||||||
|
pub CallIntMethodA: *mut c_void,
|
||||||
pub CallLongMethod: *mut c_void,
|
pub CallLongMethod: *mut c_void,
|
||||||
|
pub CallLongMethodV: *mut c_void,
|
||||||
|
pub CallLongMethodA: *mut c_void,
|
||||||
pub CallFloatMethod: *mut c_void,
|
pub CallFloatMethod: *mut c_void,
|
||||||
|
pub CallFloatMethodV: *mut c_void,
|
||||||
|
pub CallFloatMethodA: *mut c_void,
|
||||||
pub CallDoubleMethod: *mut c_void,
|
pub CallDoubleMethod: *mut c_void,
|
||||||
pub CallVoidMethod: *mut c_void,
|
pub CallDoubleMethodV: *mut c_void,
|
||||||
|
pub CallDoubleMethodA: *mut c_void,
|
||||||
|
pub CallVoidMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, obj: *mut c_void, methodID: *mut c_void)>,
|
||||||
|
pub CallVoidMethodV: *mut c_void,
|
||||||
|
pub CallVoidMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualObjectMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualObjectMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualObjectMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualBooleanMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualBooleanMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualBooleanMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualByteMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualByteMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualByteMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualCharMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualCharMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualCharMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualShortMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualShortMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualShortMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualIntMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualIntMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualIntMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualLongMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualLongMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualLongMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualFloatMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualFloatMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualFloatMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualDoubleMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualDoubleMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualDoubleMethodA: *mut c_void,
|
||||||
|
pub CallNonvirtualVoidMethod: *mut c_void,
|
||||||
|
pub CallNonvirtualVoidMethodV: *mut c_void,
|
||||||
|
pub CallNonvirtualVoidMethodA: *mut c_void,
|
||||||
|
pub GetFieldID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||||
|
pub GetObjectField: *mut c_void,
|
||||||
|
pub GetBooleanField: *mut c_void,
|
||||||
|
pub GetByteField: *mut c_void,
|
||||||
|
pub GetCharField: *mut c_void,
|
||||||
|
pub GetShortField: *mut c_void,
|
||||||
|
pub GetIntField: *mut c_void,
|
||||||
|
pub GetLongField: *mut c_void,
|
||||||
|
pub GetFloatField: *mut c_void,
|
||||||
|
pub GetDoubleField: *mut c_void,
|
||||||
|
pub SetObjectField: *mut c_void,
|
||||||
|
pub SetBooleanField: *mut c_void,
|
||||||
|
pub SetByteField: *mut c_void,
|
||||||
|
pub SetCharField: *mut c_void,
|
||||||
|
pub SetShortField: *mut c_void,
|
||||||
|
pub SetIntField: *mut c_void,
|
||||||
|
pub SetLongField: *mut c_void,
|
||||||
|
pub SetFloatField: *mut c_void,
|
||||||
|
pub SetDoubleField: *mut c_void,
|
||||||
pub GetStaticMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
pub GetStaticMethodID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||||
pub CallStaticObjectMethod: *mut c_void,
|
pub CallStaticObjectMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> *mut c_void>,
|
||||||
pub CallStaticBooleanMethod: *mut c_void,
|
pub CallStaticObjectMethodV: *mut c_void,
|
||||||
|
pub CallStaticObjectMethodA: *mut c_void,
|
||||||
|
pub CallStaticBooleanMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> u8>,
|
||||||
|
pub CallStaticBooleanMethodV: *mut c_void,
|
||||||
|
pub CallStaticBooleanMethodA: *mut c_void,
|
||||||
pub CallStaticByteMethod: *mut c_void,
|
pub CallStaticByteMethod: *mut c_void,
|
||||||
|
pub CallStaticByteMethodV: *mut c_void,
|
||||||
|
pub CallStaticByteMethodA: *mut c_void,
|
||||||
pub CallStaticCharMethod: *mut c_void,
|
pub CallStaticCharMethod: *mut c_void,
|
||||||
|
pub CallStaticCharMethodV: *mut c_void,
|
||||||
|
pub CallStaticCharMethodA: *mut c_void,
|
||||||
pub CallStaticShortMethod: *mut c_void,
|
pub CallStaticShortMethod: *mut c_void,
|
||||||
pub CallStaticIntMethod: *mut c_void,
|
pub CallStaticShortMethodV: *mut c_void,
|
||||||
|
pub CallStaticShortMethodA: *mut c_void,
|
||||||
|
pub CallStaticIntMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void) -> i32>,
|
||||||
|
pub CallStaticIntMethodV: *mut c_void,
|
||||||
|
pub CallStaticIntMethodA: *mut c_void,
|
||||||
pub CallStaticLongMethod: *mut c_void,
|
pub CallStaticLongMethod: *mut c_void,
|
||||||
|
pub CallStaticLongMethodV: *mut c_void,
|
||||||
|
pub CallStaticLongMethodA: *mut c_void,
|
||||||
pub CallStaticFloatMethod: *mut c_void,
|
pub CallStaticFloatMethod: *mut c_void,
|
||||||
|
pub CallStaticFloatMethodV: *mut c_void,
|
||||||
|
pub CallStaticFloatMethodA: *mut c_void,
|
||||||
pub CallStaticDoubleMethod: *mut c_void,
|
pub CallStaticDoubleMethod: *mut c_void,
|
||||||
pub CallStaticVoidMethod: *mut c_void,
|
pub CallStaticDoubleMethodV: *mut c_void,
|
||||||
pub GetFieldID: *mut c_void,
|
pub CallStaticDoubleMethodA: *mut c_void,
|
||||||
pub GetStaticFieldID: *mut c_void,
|
pub CallStaticVoidMethod: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methodID: *mut c_void)>,
|
||||||
|
pub CallStaticVoidMethodV: *mut c_void,
|
||||||
|
pub CallStaticVoidMethodA: *mut c_void,
|
||||||
|
pub GetStaticFieldID: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, name: *const libc::c_char, sig: *const libc::c_char) -> *mut c_void>,
|
||||||
|
pub GetStaticObjectField: *mut c_void,
|
||||||
|
pub GetStaticBooleanField: *mut c_void,
|
||||||
|
pub GetStaticByteField: *mut c_void,
|
||||||
|
pub GetStaticCharField: *mut c_void,
|
||||||
|
pub GetStaticShortField: *mut c_void,
|
||||||
|
pub GetStaticIntField: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, fieldID: *mut c_void) -> i32>,
|
||||||
|
pub GetStaticLongField: *mut c_void,
|
||||||
|
pub GetStaticFloatField: *mut c_void,
|
||||||
|
pub GetStaticDoubleField: *mut c_void,
|
||||||
|
pub SetStaticObjectField: *mut c_void,
|
||||||
|
pub SetStaticBooleanField: *mut c_void,
|
||||||
|
pub SetStaticByteField: *mut c_void,
|
||||||
|
pub SetStaticCharField: *mut c_void,
|
||||||
|
pub SetStaticShortField: *mut c_void,
|
||||||
|
pub SetStaticIntField: *mut c_void,
|
||||||
|
pub SetStaticLongField: *mut c_void,
|
||||||
|
pub SetStaticFloatField: *mut c_void,
|
||||||
|
pub SetStaticDoubleField: *mut c_void,
|
||||||
|
pub NewString: *mut c_void,
|
||||||
|
pub GetStringLength: *mut c_void,
|
||||||
|
pub GetStringChars: *mut c_void,
|
||||||
|
pub ReleaseStringChars: *mut c_void,
|
||||||
pub NewStringUTF: Option<unsafe extern "C" fn(env: JNIEnvPtr, bytes: *const libc::c_char) -> *mut c_void>,
|
pub NewStringUTF: Option<unsafe extern "C" fn(env: JNIEnvPtr, bytes: *const libc::c_char) -> *mut c_void>,
|
||||||
pub GetStringUTFLength: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void) -> i32>,
|
pub GetStringUTFLength: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void) -> i32>,
|
||||||
pub GetStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, isCopy: *mut u8) -> *const libc::c_char>,
|
pub GetStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, isCopy: *mut u8) -> *const libc::c_char>,
|
||||||
pub ReleaseStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, utf: *const libc::c_char)>,
|
pub ReleaseStringUTFChars: Option<unsafe extern "C" fn(env: JNIEnvPtr, string: *mut c_void, utf: *const libc::c_char)>,
|
||||||
|
pub GetArrayLength: Option<unsafe extern "C" fn(env: JNIEnvPtr, array: *mut c_void) -> i32>,
|
||||||
|
pub NewObjectArray: *mut c_void,
|
||||||
|
pub GetObjectArrayElement: *mut c_void,
|
||||||
|
pub SetObjectArrayElement: *mut c_void,
|
||||||
|
pub NewBooleanArray: *mut c_void,
|
||||||
|
pub NewByteArray: *mut c_void,
|
||||||
|
pub NewCharArray: *mut c_void,
|
||||||
|
pub NewShortArray: *mut c_void,
|
||||||
|
pub NewIntArray: *mut c_void,
|
||||||
|
pub NewLongArray: *mut c_void,
|
||||||
|
pub NewFloatArray: *mut c_void,
|
||||||
|
pub NewDoubleArray: *mut c_void,
|
||||||
|
pub GetBooleanArrayElements: *mut c_void,
|
||||||
|
pub GetByteArrayElements: *mut c_void,
|
||||||
|
pub GetCharArrayElements: *mut c_void,
|
||||||
|
pub GetShortArrayElements: *mut c_void,
|
||||||
|
pub GetIntArrayElements: *mut c_void,
|
||||||
|
pub GetLongArrayElements: *mut c_void,
|
||||||
|
pub GetFloatArrayElements: *mut c_void,
|
||||||
|
pub GetDoubleArrayElements: *mut c_void,
|
||||||
|
pub ReleaseBooleanArrayElements: *mut c_void,
|
||||||
|
pub ReleaseByteArrayElements: *mut c_void,
|
||||||
|
pub ReleaseCharArrayElements: *mut c_void,
|
||||||
|
pub ReleaseShortArrayElements: *mut c_void,
|
||||||
|
pub ReleaseIntArrayElements: *mut c_void,
|
||||||
|
pub ReleaseLongArrayElements: *mut c_void,
|
||||||
|
pub ReleaseFloatArrayElements: *mut c_void,
|
||||||
|
pub ReleaseDoubleArrayElements: *mut c_void,
|
||||||
|
pub GetBooleanArrayRegion: *mut c_void,
|
||||||
|
pub GetByteArrayRegion: *mut c_void,
|
||||||
|
pub GetCharArrayRegion: *mut c_void,
|
||||||
|
pub GetShortArrayRegion: *mut c_void,
|
||||||
|
pub GetIntArrayRegion: *mut c_void,
|
||||||
|
pub GetLongArrayRegion: *mut c_void,
|
||||||
|
pub GetFloatArrayRegion: *mut c_void,
|
||||||
|
pub GetDoubleArrayRegion: *mut c_void,
|
||||||
|
pub SetBooleanArrayRegion: *mut c_void,
|
||||||
|
pub SetByteArrayRegion: *mut c_void,
|
||||||
|
pub SetCharArrayRegion: *mut c_void,
|
||||||
|
pub SetShortArrayRegion: *mut c_void,
|
||||||
|
pub SetIntArrayRegion: *mut c_void,
|
||||||
|
pub SetLongArrayRegion: *mut c_void,
|
||||||
|
pub SetFloatArrayRegion: *mut c_void,
|
||||||
|
pub SetDoubleArrayRegion: *mut c_void,
|
||||||
|
pub RegisterNatives: Option<unsafe extern "C" fn(env: JNIEnvPtr, clazz: *mut c_void, methods: *const c_void, nMethods: i32) -> i32>,
|
||||||
|
pub UnregisterNatives: *mut c_void,
|
||||||
|
pub MonitorEnter: *mut c_void,
|
||||||
|
pub MonitorExit: *mut c_void,
|
||||||
|
pub GetJavaVM: Option<unsafe extern "C" fn(env: JNIEnvPtr, vm: *mut JavaVMPtr) -> i32>,
|
||||||
|
pub GetStringRegion: *mut c_void,
|
||||||
|
pub GetStringUTFRegion: *mut c_void,
|
||||||
|
pub GetPrimitiveArrayCritical: *mut c_void,
|
||||||
|
pub ReleasePrimitiveArrayCritical: *mut c_void,
|
||||||
|
pub GetStringCritical: *mut c_void,
|
||||||
|
pub ReleaseStringCritical: *mut c_void,
|
||||||
|
pub NewWeakGlobalRef: *mut c_void,
|
||||||
|
pub DeleteWeakGlobalRef: *mut c_void,
|
||||||
|
pub ExceptionCheck: Option<unsafe extern "C" fn(env: JNIEnvPtr) -> u8>,
|
||||||
|
pub NewDirectByteBuffer: *mut c_void,
|
||||||
|
pub GetDirectBufferAddress: *mut c_void,
|
||||||
|
pub GetDirectBufferCapacity: *mut c_void,
|
||||||
|
pub GetObjectRefType: *mut c_void,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code, non_snake_case)]
|
||||||
use super::env::JNIEnvWrapper;
|
use super::env::GLOBAL_JNIENV;
|
||||||
use super::types::{JNIInvokeInterface, JavaVMPtr, JNI_OK, JNI_VERSION_1_6};
|
use super::types::{JNIInvokeInterface, JavaVMPtr, JNI_OK, JNI_VERSION_1_6};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
unsafe extern "C" fn jni_get_env(_vm: JavaVMPtr, penv: *mut *mut c_void, _version: i32) -> i32 {
|
unsafe extern "C" fn jni_get_env(_vm: JavaVMPtr, penv: *mut *mut c_void, version: i32) -> i32 {
|
||||||
if penv.is_null() {
|
if penv.is_null() {
|
||||||
return super::types::JNI_ERR;
|
return super::types::JNI_ERR;
|
||||||
}
|
}
|
||||||
// Return standard JNIEnv
|
if version != JNI_VERSION_1_6 {
|
||||||
let env = Box::new(JNIEnvWrapper::new());
|
tracing::warn!("JNI: requested unsupported version 0x{:x}", version);
|
||||||
*penv = env.as_raw_ptr() as *mut c_void;
|
}
|
||||||
std::mem::forget(env);
|
*penv = GLOBAL_JNIENV.as_raw_ptr() as *mut c_void;
|
||||||
JNI_OK
|
JNI_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,14 @@ unsafe extern "C" fn jni_detach_current_thread(_vm: JavaVMPtr) -> i32 {
|
||||||
|
|
||||||
pub struct JavaVMWrapper {
|
pub struct JavaVMWrapper {
|
||||||
table: Box<JNIInvokeInterface>,
|
table: Box<JNIInvokeInterface>,
|
||||||
table_ptr: *const JNIInvokeInterface,
|
pinned_ptr: Box<*const JNIInvokeInterface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for JavaVMWrapper {}
|
||||||
|
unsafe impl Sync for JavaVMWrapper {}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref GLOBAL_JAVAVM: JavaVMWrapper = JavaVMWrapper::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JavaVMWrapper {
|
impl Default for JavaVMWrapper {
|
||||||
|
|
@ -48,12 +55,13 @@ impl JavaVMWrapper {
|
||||||
AttachCurrentThreadAsDaemon: Some(jni_attach_current_thread),
|
AttachCurrentThreadAsDaemon: Some(jni_attach_current_thread),
|
||||||
});
|
});
|
||||||
|
|
||||||
let table_ptr = &*table as *const JNIInvokeInterface;
|
let table_raw = &*table as *const JNIInvokeInterface;
|
||||||
|
let pinned_ptr = Box::new(table_raw);
|
||||||
|
|
||||||
Self { table, table_ptr }
|
Self { table, pinned_ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_raw_ptr(&self) -> JavaVMPtr {
|
pub fn as_raw_ptr(&self) -> JavaVMPtr {
|
||||||
&self.table_ptr as *const *const JNIInvokeInterface as JavaVMPtr
|
&*self.pinned_ptr as *const *const JNIInvokeInterface as JavaVMPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
crates/molotov-core/tests/jni_abi_fixes_test.rs
Normal file
53
crates/molotov-core/tests/jni_abi_fixes_test.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
use molotov_core::jni::activity::ActivityRunner;
|
||||||
|
use molotov_core::jni::types::JNINativeInterface;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jni_native_interface_size_matches_standard_spec() {
|
||||||
|
// 233 function pointers * 8 bytes = 1864 bytes on 64-bit systems
|
||||||
|
assert_eq!(
|
||||||
|
std::mem::size_of::<JNINativeInterface>(),
|
||||||
|
1864,
|
||||||
|
"JNINativeInterface must strictly match standard JNI 233-pointer ABI specification"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_string_utf_and_get_string_utf_chars_via_jni_env() {
|
||||||
|
let runner = ActivityRunner::new();
|
||||||
|
let env = runner.env_ptr();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let new_string_utf = (**env).NewStringUTF.expect("NewStringUTF must be implemented");
|
||||||
|
let test_c_str = CString::new("roblox_test_string").unwrap();
|
||||||
|
let jstr = new_string_utf(env, test_c_str.as_ptr());
|
||||||
|
assert!(!jstr.is_null(), "NewStringUTF must return non-null jobject");
|
||||||
|
|
||||||
|
let get_string_utf = (**env).GetStringUTFChars.expect("GetStringUTFChars must be implemented");
|
||||||
|
let mut is_copy = 0u8;
|
||||||
|
let utf_chars = get_string_utf(env, jstr, &mut is_copy);
|
||||||
|
assert!(!utf_chars.is_null());
|
||||||
|
assert_eq!(CStr::from_ptr(utf_chars).to_str().unwrap(), "roblox_test_string");
|
||||||
|
|
||||||
|
let release_string_utf = (**env).ReleaseStringUTFChars.expect("ReleaseStringUTFChars must be implemented");
|
||||||
|
release_string_utf(env, jstr, utf_chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_class_and_device_profile_via_jni_env() {
|
||||||
|
let runner = ActivityRunner::new();
|
||||||
|
let env = runner.env_ptr();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let find_class = (**env).FindClass.expect("FindClass must be implemented");
|
||||||
|
let class_name = CString::new("android/os/Build").unwrap();
|
||||||
|
let clazz = find_class(env, class_name.as_ptr());
|
||||||
|
assert!(!clazz.is_null(), "android/os/Build must be recognized by AndroidEnvironment");
|
||||||
|
|
||||||
|
let bogus_name = CString::new("non/existent/Class").unwrap();
|
||||||
|
let bogus = find_class(env, bogus_name.as_ptr());
|
||||||
|
assert!(bogus.is_null(), "Unrecognized classes must return NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,93 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
use molotov_core::jni::activity::{ANativeActivity, ActivityRunner};
|
use molotov_core::jni::activity::{ANativeActivity, ActivityRunner};
|
||||||
use molotov_core::jni::types::{JavaVMPtr, JNI_VERSION_1_6};
|
use molotov_core::jni::types::{JavaVMPtr, JNI_VERSION_1_6};
|
||||||
|
use std::ffi::{c_void, CStr, CString};
|
||||||
|
|
||||||
|
static mut RESUME_CALLED: bool = false;
|
||||||
|
static mut WINDOW_CREATED: bool = false;
|
||||||
|
|
||||||
// Simulated JNI_OnLoad exported by libroblox.so
|
|
||||||
unsafe extern "C" fn mock_jni_onload(vm: JavaVMPtr, _reserved: *mut ()) -> i32 {
|
unsafe extern "C" fn mock_jni_onload(vm: JavaVMPtr, _reserved: *mut ()) -> i32 {
|
||||||
let mut env = std::ptr::null_mut();
|
let mut env = std::ptr::null_mut();
|
||||||
let res = (**vm).GetEnv.unwrap()(vm, &mut env, JNI_VERSION_1_6);
|
let res = (**vm).GetEnv.unwrap()(vm, &mut env, JNI_VERSION_1_6);
|
||||||
if res == 0 && !env.is_null() {
|
if res != 0 || env.is_null() {
|
||||||
JNI_VERSION_1_6
|
return -1;
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
}
|
||||||
|
let jni_env = env as molotov_core::jni::types::JNIEnvPtr;
|
||||||
|
|
||||||
|
// Verify JNIEnv can find classes
|
||||||
|
let class_name = CString::new("android/os/Build").unwrap();
|
||||||
|
let build_class = (**jni_env).FindClass.unwrap()(jni_env, class_name.as_ptr());
|
||||||
|
if build_class.is_null() {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify String creation
|
||||||
|
let test_str = CString::new("RobloxEngine_Init").unwrap();
|
||||||
|
let jstr = (**jni_env).NewStringUTF.unwrap()(jni_env, test_str.as_ptr());
|
||||||
|
if jstr.is_null() {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf = (**jni_env).GetStringUTFChars.unwrap()(jni_env, jstr, std::ptr::null_mut());
|
||||||
|
if CStr::from_ptr(utf).to_str().unwrap() != "RobloxEngine_Init" {
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
(**jni_env).ReleaseStringUTFChars.unwrap()(jni_env, jstr, utf);
|
||||||
|
|
||||||
|
JNI_VERSION_1_6
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulated ANativeActivity_onCreate exported by libroblox.so
|
|
||||||
unsafe extern "C" fn mock_native_activity_on_create(
|
unsafe extern "C" fn mock_native_activity_on_create(
|
||||||
activity: *mut ANativeActivity,
|
activity: *mut ANativeActivity,
|
||||||
_saved_state: *mut (),
|
_saved_state: *mut (),
|
||||||
_saved_state_size: usize,
|
_saved_state_size: usize,
|
||||||
) {
|
) {
|
||||||
if let Some(act) = activity.as_mut() {
|
if let Some(act) = activity.as_mut() {
|
||||||
|
// Assert internalDataPath is non-null and valid
|
||||||
|
assert!(!act.internalDataPath.is_null());
|
||||||
|
let path = CStr::from_ptr(act.internalDataPath).to_str().unwrap();
|
||||||
|
assert!(path.contains("molotov"));
|
||||||
|
|
||||||
if let Some(cbs) = act.callbacks.as_mut() {
|
if let Some(cbs) = act.callbacks.as_mut() {
|
||||||
cbs.onResume = Some(mock_on_resume);
|
cbs.onResume = Some(mock_on_resume);
|
||||||
|
cbs.onNativeWindowCreated = Some(mock_on_window_created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static mut RESUME_CALLED: bool = false;
|
|
||||||
unsafe extern "C" fn mock_on_resume(_activity: *mut ANativeActivity) {
|
unsafe extern "C" fn mock_on_resume(_activity: *mut ANativeActivity) {
|
||||||
RESUME_CALLED = true;
|
RESUME_CALLED = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn mock_on_window_created(_activity: *mut ANativeActivity, window: *mut c_void) {
|
||||||
|
if !window.is_null() {
|
||||||
|
WINDOW_CREATED = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_jni_onload_and_native_activity_lifecycle() {
|
fn test_jni_onload_and_native_activity_lifecycle() {
|
||||||
let mut runner = ActivityRunner::new();
|
let mut runner = ActivityRunner::new();
|
||||||
|
|
||||||
// 1. Simulate JNI_OnLoad execution
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ver = mock_jni_onload(runner.activity.vm, std::ptr::null_mut());
|
// 1. Simulate JNI_OnLoad execution
|
||||||
|
let ver = mock_jni_onload(runner.vm_ptr(), std::ptr::null_mut());
|
||||||
assert_eq!(ver, JNI_VERSION_1_6, "JNI_OnLoad must return JNI_VERSION_1_6");
|
assert_eq!(ver, JNI_VERSION_1_6, "JNI_OnLoad must return JNI_VERSION_1_6");
|
||||||
|
|
||||||
// 2. Simulate ANativeActivity_onCreate
|
// 2. Simulate ANativeActivity_onCreate
|
||||||
mock_native_activity_on_create(&mut *runner.activity, std::ptr::null_mut(), 0);
|
mock_native_activity_on_create(&mut *runner.activity, std::ptr::null_mut(), 0);
|
||||||
|
|
||||||
// 3. Trigger Resume callback registered by the engine
|
// 3. Trigger Lifecycle
|
||||||
|
runner.trigger_start();
|
||||||
runner.trigger_resume();
|
runner.trigger_resume();
|
||||||
assert!(RESUME_CALLED, "Registered onResume callback must be invoked");
|
assert!(RESUME_CALLED, "Registered onResume callback must be invoked");
|
||||||
|
|
||||||
|
let dummy_window = 0x1234_5678 as *mut c_void;
|
||||||
|
runner.trigger_window_created(dummy_window);
|
||||||
|
assert!(WINDOW_CREATED, "onNativeWindowCreated callback must be invoked");
|
||||||
|
|
||||||
|
runner.trigger_pause();
|
||||||
|
runner.trigger_stop();
|
||||||
|
runner.trigger_destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue