use std::{
ffi::{self},
marker::PhantomData,
mem::MaybeUninit,
ptr::{self, NonNull},
};
use jni_sys::jvalue;
use crate::{jvm::JavaObjectExt, plumbing::ToJavaScalar, Error, JavaObject, Jvm, Local};
#[cfg(any(
all(
not(any(feature = "jni_1_6", feature = "jni_1_8",)),
not(target_os = "android")
),
feature = "jni_1_8"
))]
const VERSION: jni_sys::jint = jni_sys::JNI_VERSION_1_8;
#[cfg(any(
all(
not(any(feature = "jni_1_6", feature = "jni_1_8",)),
target_os = "android"
),
all(feature = "jni_1_6", not(feature = "jni_1_8"))
))]
const VERSION: jni_sys::jint = jni_sys::JNI_VERSION_1_6;
#[cfg(all(target_os = "android", feature = "jni_1_8"))]
std::compile_error!("Set to use JNI API 1.8+ when compiling for Android, invalid. (Android supports JNI 1.6 and below)");
pub(crate) unsafe fn existing_jvm() -> crate::Result<Option<JvmPtr>> {
let libjvm = crate::libjvm::libjvm_or_load()?;
let mut jvms = [std::ptr::null_mut::<jni_sys::JavaVM>()];
let mut num_jvms: jni_sys::jsize = 0;
let code = unsafe {
(libjvm.JNI_GetCreatedJavaVMs)(
jvms.as_mut_ptr(),
jvms.len().try_into().unwrap(),
&mut num_jvms as *mut _,
)
};
if code != jni_sys::JNI_OK {
return Err(Error::JvmInternal(format!(
"GetCreatedJavaVMs failed with code `{code}`"
)));
}
match num_jvms {
0 => Ok(None),
1 => JvmPtr::new(jvms[0])
.ok_or_else(|| Error::JvmInternal("GetCreatedJavaVMs returned null pointer".into()))
.map(Some),
_ => Err(Error::JvmInternal(format!(
"GetCreatedJavaVMs returned more JVMs than expected: `{num_jvms}`"
))),
}
}
pub(crate) unsafe fn try_create_jvm<'a>(
options: impl IntoIterator<Item = String>,
) -> crate::Result<JvmPtr> {
let libjvm = crate::libjvm::libjvm_or_load()?;
let options = options
.into_iter()
.map(|opt| ffi::CString::new(opt).unwrap())
.collect::<Vec<_>>();
let mut option_ptrs = options
.iter()
.map(|opt| jni_sys::JavaVMOption {
optionString: opt.as_ptr().cast_mut(),
extraInfo: std::ptr::null_mut(),
})
.collect::<Vec<_>>();
let mut args = jni_sys::JavaVMInitArgs {
version: VERSION,
nOptions: options.len().try_into().unwrap(),
options: option_ptrs.as_mut_ptr(),
ignoreUnrecognized: jni_sys::JNI_FALSE,
};
let mut jvm = std::ptr::null_mut::<jni_sys::JavaVM>();
let mut env = std::ptr::null_mut::<ffi::c_void>();
let code = unsafe {
(libjvm.JNI_CreateJavaVM)(
&mut jvm as *mut _,
&mut env as *mut _,
&mut args as *mut _ as *mut ffi::c_void,
)
};
match code {
jni_sys::JNI_OK => {
let Some(jvm) = JvmPtr::new(jvm) else {
return Err(Error::JvmInternal(
"JNI_CreateJavaVM returned null pointer".into(),
));
};
unsafe { jvm.detach_thread() }?;
Ok(jvm)
}
jni_sys::JNI_EEXIST => Err(Error::JvmAlreadyExists),
_ => Err(Error::JvmInternal(format!(
"CreateJavaVM failed with code `{code}`"
))),
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JvmPtr(NonNull<jni_sys::JavaVM>);
impl JvmPtr {
pub(crate) fn new(ptr: *mut jni_sys::JavaVM) -> Option<Self> {
NonNull::new(ptr).map(Self)
}
pub(crate) unsafe fn env<'jvm>(self) -> crate::Result<Option<EnvPtr<'jvm>>> {
let mut env_ptr = std::ptr::null_mut::<ffi::c_void>();
match fn_table_call(
self.0,
|jvm| jvm.GetEnv,
|jvm, f| f(jvm, &mut env_ptr as *mut _, VERSION),
) {
jni_sys::JNI_OK => Ok(Some(EnvPtr::new(env_ptr.cast()).unwrap())),
jni_sys::JNI_EDETACHED => Ok(None),
code => Err(Error::JvmInternal(format!(
"GetEnv failed with code `{code}`"
))),
}
}
pub(crate) unsafe fn attach_thread<'jvm>(self) -> crate::Result<EnvPtr<'jvm>> {
let mut env_ptr = std::ptr::null_mut::<ffi::c_void>();
match fn_table_call(
self.0,
|jvm| jvm.AttachCurrentThread,
|jvm, f| {
f(
jvm,
&mut env_ptr as *mut _,
std::ptr::null_mut(),
)
},
) {
jni_sys::JNI_OK => Ok(EnvPtr::new(env_ptr.cast()).unwrap()),
code => Err(Error::JvmInternal(format!(
"AttachCurrentThread failed with code `{code}`"
))),
}
}
pub(crate) unsafe fn detach_thread(self) -> crate::Result<()> {
match fn_table_call(self.0, |jvm| jvm.DetachCurrentThread, |jvm, f| f(jvm)) {
jni_sys::JNI_OK => Ok(()),
code => Err(Error::JvmInternal(format!(
"DetachCurrentThread failed with code `{code}`"
))),
}
}
}
unsafe fn fn_table_call<T, F, R>(
table_ptr: NonNull<*const T>,
fn_field: impl FnOnce(&T) -> Option<F>,
call: impl FnOnce(*mut *const T, F) -> R,
) -> R {
let fn_field = fn_field(&**table_ptr.as_ptr());
let fn_field = fn_field.unwrap_unchecked();
call(table_ptr.as_ptr(), fn_field)
}
unsafe impl Send for JvmPtr {}
unsafe impl Sync for JvmPtr {}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct EnvPtr<'jvm> {
ptr: NonNull<jni_sys::JNIEnv>,
_marker: PhantomData<&'jvm ()>,
}
impl<'jvm> EnvPtr<'jvm> {
pub(crate) unsafe fn new(ptr: *mut jni_sys::JNIEnv) -> Option<Self> {
let ptr = NonNull::new(ptr)?;
Some(Self {
ptr,
_marker: PhantomData,
})
}
#[doc(hidden)]
pub unsafe fn invoke<F, T: FromJniValue<'jvm>>(
self,
fn_field: impl FnOnce(&jni_sys::JNINativeInterface_) -> Option<F>,
call: impl FnOnce(*mut jni_sys::JNIEnv, F) -> T::JniValue,
) -> crate::LocalResult<'jvm, T> {
let value = self.invoke_unchecked(fn_field, call);
let value = T::from_jni_value(self, value);
self.check_exception()?;
Ok(value)
}
pub(crate) unsafe fn invoke_unchecked<F, T>(
self,
fn_field: impl FnOnce(&jni_sys::JNINativeInterface_) -> Option<F>,
call: impl FnOnce(*mut jni_sys::JNIEnv, F) -> T,
) -> T {
fn_table_call(self.ptr, fn_field, call)
}
pub unsafe fn jvm_ptr(self) -> Result<JvmPtr, ()> {
let env = self.ptr.as_ptr();
let get_java_vm = unsafe { (**env).GetJavaVM.ok_or(())? };
let mut jvm_ptr: MaybeUninit<*mut jni_sys::JavaVM> = MaybeUninit::uninit();
if get_java_vm(env, jvm_ptr.as_mut_ptr()) != 0 {
return Err(());
}
let jvm_ptr = jvm_ptr.assume_init();
assert!(!jvm_ptr.is_null());
JvmPtr::new(jvm_ptr).ok_or(())
}
pub unsafe fn register_native_methods(
self,
class: ObjectPtr,
native_methods: &[jni_sys::JNINativeMethod],
) -> crate::LocalResult<'jvm, ()> {
let result: jni_sys::jint = self.invoke(
|f| f.RegisterNatives,
|env, register_natives| {
let nm_ptr = native_methods.as_ptr();
let nm_len: i32 = native_methods.len() as i32;
register_natives(env, class.as_ptr(), nm_ptr, nm_len)
},
)?;
if result == 0 {
Ok(())
} else {
Err(crate::Error::JvmInternal(format!(
"register native methods failed"
)))
}
}
pub fn check_exception(self) -> crate::LocalResult<'jvm, ()> {
let thrown = unsafe { self.invoke_unchecked(|env| env.ExceptionOccurred, |env, f| f(env)) };
if let Some(thrown) = ObjectPtr::new(thrown) {
unsafe { self.invoke_unchecked(|env| env.ExceptionClear, |env, f| f(env)) };
Err(Error::Thrown(unsafe { Local::from_raw(self, thrown) }))
} else {
Ok(())
}
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectPtr(NonNull<jni_sys::_jobject>);
impl ObjectPtr {
#[doc(hidden)]
pub fn new(ptr: jni_sys::jobject) -> Option<Self> {
NonNull::new(ptr).map(Self)
}
#[doc(hidden)]
pub fn as_ptr(self) -> jni_sys::jobject {
self.0.as_ptr()
}
pub(crate) unsafe fn as_ref<'a, T: JavaObject>(self) -> &'a T {
unsafe { self.0.cast().as_ref() }
}
}
impl From<NonNull<jni_sys::_jobject>> for ObjectPtr {
fn from(ptr: NonNull<jni_sys::_jobject>) -> Self {
Self(ptr)
}
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct MethodPtr(NonNull<jni_sys::_jmethodID>);
impl MethodPtr {
pub(crate) fn new(ptr: jni_sys::jmethodID) -> Option<Self> {
NonNull::new(ptr).map(Self)
}
#[doc(hidden)]
pub fn as_ptr(self) -> jni_sys::jmethodID {
self.0.as_ptr()
}
}
unsafe impl Send for MethodPtr {}
unsafe impl Sync for MethodPtr {}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct FieldPtr(NonNull<jni_sys::_jfieldID>);
impl FieldPtr {
pub(crate) fn new(ptr: jni_sys::jfieldID) -> Option<Self> {
NonNull::new(ptr).map(Self)
}
#[doc(hidden)]
pub fn as_ptr(self) -> jni_sys::jfieldID {
self.0.as_ptr()
}
}
unsafe impl Send for FieldPtr {}
unsafe impl Sync for FieldPtr {}
#[doc(hidden)]
pub trait IntoJniValue {
fn into_jni_value(self) -> jvalue;
}
impl<T: JavaObject> IntoJniValue for &T {
fn into_jni_value(self) -> jvalue {
jvalue {
l: self.as_raw().as_ptr(),
}
}
}
impl<T: JavaObject> IntoJniValue for Option<&T> {
fn into_jni_value(self) -> jvalue {
self.map(|v| v.into_jni_value())
.unwrap_or(jvalue { l: ptr::null_mut() })
}
}
#[doc(hidden)]
pub trait FromJniValue<'jvm> {
type JniValue;
unsafe fn from_jni_value(env: EnvPtr<'jvm>, value: Self::JniValue) -> Self;
}
impl<'jvm, T: JavaObject> FromJniValue<'jvm> for Option<Local<'jvm, T>> {
type JniValue = jni_sys::jobject;
unsafe fn from_jni_value(env: EnvPtr<'jvm>, value: Self::JniValue) -> Self {
ObjectPtr::new(value).map(|obj| unsafe { Local::from_raw(env, obj) })
}
}
impl<'jvm> FromJniValue<'jvm> for () {
type JniValue = ();
unsafe fn from_jni_value(_env: EnvPtr<'jvm>, _value: Self::JniValue) -> Self {
()
}
}
macro_rules! scalar_jni_value {
($($rust:ty: $field:ident $java:ident,)*) => {
$(
impl IntoJniValue for $rust {
fn into_jni_value(self) -> jvalue {
jvalue {
$field: self as jni_sys::$java,
}
}
}
impl<'jvm> FromJniValue<'jvm> for $rust {
type JniValue = jni_sys::$java;
unsafe fn from_jni_value(_env: EnvPtr<'jvm>, value: Self::JniValue) -> Self {
value
}
}
impl ToJavaScalar<$rust> for $rust {
fn to_java_scalar<'jvm>(rust: &Self, _jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, $rust> {
Ok(*rust)
}
}
)*
};
}
scalar_jni_value! {
i8: b jbyte,
i16: s jshort,
u16: c jchar,
i32: i jint,
i64: j jlong,
f32: f jfloat,
f64: d jdouble,
}
impl IntoJniValue for bool {
fn into_jni_value(self) -> jvalue {
jvalue {
z: self as jni_sys::jboolean,
}
}
}
impl<'jvm> FromJniValue<'jvm> for bool {
type JniValue = jni_sys::jboolean;
unsafe fn from_jni_value(_env: EnvPtr<'jvm>, value: Self::JniValue) -> Self {
value == jni_sys::JNI_TRUE
}
}