#![allow(non_snake_case)]
use crate::paths;
use std::error::Error;
use std::fmt::{self, Debug, Display, Formatter};
use std::io;
use std::os::raw::c_void;
use std::path::{Path};
use std::ptr::*;
use jni_sys::*;
#[cfg(unix)] use libc::*;
#[cfg(windows)] pub const ERROR_BAD_EXE_FORMAT : u32 = 0x00C1;
#[cfg(windows)] extern "system" {
pub fn GetModuleHandleA(lpModuleName: *const i8) -> *mut c_void;
pub fn GetProcAddress(hModule: *mut c_void, lpProcName: *const i8) -> *mut c_void;
pub fn LoadLibraryW(lpFileName: *const u16) -> *mut c_void;
}
#[derive(Debug)]
pub struct LoadError(io::Error);
impl Display for LoadError { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { Display::fmt(&self.0, fmt) } }
impl Error for LoadError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.0.source() } }
impl From<io::Error> for LoadError { fn from(error: io::Error) -> Self { Self(error) } }
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JniError(jint);
impl JniError {
pub const OK : JniError = JniError(JNI_OK);
pub const DETACHED : JniError = JniError(JNI_EDETACHED);
pub const VERSION : JniError = JniError(JNI_EVERSION);
pub const NOMEM : JniError = JniError(JNI_ENOMEM);
pub const EXIST : JniError = JniError(JNI_EEXIST);
pub const INVAL : JniError = JniError(JNI_EINVAL);
}
impl Debug for JniError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
Display::fmt(self, fmt)
}
}
impl Display for JniError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
match self.0 {
jni_sys::JNI_OK => write!(fmt, "JNI_OK"),
jni_sys::JNI_EDETACHED => write!(fmt, "JNI_EDETACHED"),
jni_sys::JNI_EVERSION => write!(fmt, "JNI_EVERSION"),
jni_sys::JNI_ENOMEM => write!(fmt, "JNI_ENOMEM"),
jni_sys::JNI_EEXIST => write!(fmt, "JNI_EEXIST"),
jni_sys::JNI_EINVAL => write!(fmt, "JNI_EINVAL"),
unknown => write!(fmt, "JNI_??? ({})", unknown),
}
}
}
impl Error for JniError {}
struct JVMAPI {
JNI_CreateJavaVM: unsafe extern "system" fn (pvm: *mut *mut JavaVM, penv: *mut *mut c_void, args: *mut c_void) -> jint,
JNI_GetCreatedJavaVMs: unsafe extern "system" fn (vmBuf: *mut *mut JavaVM, bufLen: jsize, nVMs: *mut jsize) -> jint,
JNI_GetDefaultJavaVMInitArgs: unsafe extern "system" fn (args: *mut c_void) -> jint,
}
impl JVMAPI {
pub unsafe fn load(module: *mut c_void) -> io::Result<Self> {
if module == null_mut() {
return Err(io::Error::last_os_error());
}
Ok(Self {
JNI_CreateJavaVM: std::mem::transmute(sym(module, "JNI_CreateJavaVM\0")?),
JNI_GetCreatedJavaVMs: std::mem::transmute(sym(module, "JNI_GetCreatedJavaVMs\0")?),
JNI_GetDefaultJavaVMInitArgs: std::mem::transmute(sym(module, "JNI_GetDefaultJavaVMInitArgs\0")?),
})
}
}
unsafe fn sym(
module: *mut c_void,
name: &'static str,
) -> io::Result<*mut c_void> {
assert!(name.ends_with('\0'));
let cname = name.as_ptr() as _;
#[cfg(windows)] let result = GetProcAddress(module as _, cname) as _;
#[cfg(unix)] let result = dlsym(module, cname);
if result == null_mut() {
Err(io::Error::new(io::ErrorKind::InvalidData, format!("Symbol {:?} missing from JVM library", &name[..name.len()-1])))
} else {
Ok(result)
}
}
pub struct Library {
jvm: JVMAPI,
}
impl Library {
pub fn get() -> Result<Library, LoadError> {
Self::from_already_loaded()
.or_else(|_| Self::from_system())
}
pub fn from_already_loaded() -> Result<Library, LoadError> {
let this_module;
#[cfg(windows)] unsafe {
this_module = GetModuleHandleA(null()) as *mut c_void;
if this_module == null_mut() { return Err(LoadError(io::Error::last_os_error())); }
}
#[cfg(unix)] {
this_module = RTLD_DEFAULT;
}
let jvm = unsafe { JVMAPI::load(this_module) }?;
Ok(Self{jvm})
}
#[cfg_attr(feature = "nightly", doc(cfg(not(target_os = "android"))))] pub fn from_system() -> Result<Library, LoadError> {
let java_home = paths::java_home()?;
Self::from_java_home(&java_home)
}
#[cfg_attr(feature = "nightly", doc(cfg(not(target_os = "android"))))] pub fn from_java_home(java_home: &(impl AsRef<Path> + ?Sized)) -> Result<Library, LoadError> {
let java_home = java_home.as_ref();
let libjvm_dir = paths::libjvm_dir(&java_home)?;
Self::from_library_path(&libjvm_dir.join(paths::libjvm_name()))
}
#[cfg_attr(feature = "nightly", doc(cfg(not(target_os = "android"))))] pub fn from_library_path(libjvm: &(impl AsRef<Path> + ?Sized)) -> Result<Library, LoadError> {
let libjvm = libjvm.as_ref();
#[cfg(windows)] let handle = unsafe {
use std::os::windows::ffi::OsStrExt;
let lpFileName = libjvm.as_os_str().encode_wide().chain([0].iter().copied()).collect::<Vec<u16>>();
LoadLibraryW(lpFileName.as_ptr()) as _
};
#[cfg(unix)] let handle = unsafe {
use std::os::unix::ffi::OsStrExt;
let filename = libjvm.as_os_str().as_bytes().iter().copied().chain([0].iter().copied()).collect::<Vec<u8>>();
dlopen(filename.as_ptr() as _, RTLD_LAZY) as _
};
let jvm = unsafe { JVMAPI::load(handle) }
.map_err(|err| match err {
#[cfg(windows)] ref io if io.kind() == io::ErrorKind::Other && io.raw_os_error() == Some(ERROR_BAD_EXE_FORMAT as _) => {
io::Error::new(
io::ErrorKind::Other,
format!(
concat!(
"Unable to load {}: ERROR_BAD_EXE_FORMAT\r\n",
"This is likely caused by trying to use 32-bit Java from a 64-bit Rust binary or vicea versa.\r\n",
"This in turn is likely caused by not having a corresponding Java installation.\r\n"
),
libjvm.display(),
)
)
},
other => other,
})?;
Ok(Self{jvm})
}
#[cfg_attr(feature = "nightly", doc(cfg(not(target_os = "android"))))] pub fn create_java_vm(&self, mut java_vm_options: Vec<String>) -> Result<*mut JavaVM, JniError> {
for o in java_vm_options.iter_mut() {
o.push('\0');
}
let mut java_vm_options : Vec<JavaVMOption> = java_vm_options.iter_mut().map(|o| JavaVMOption {
optionString: o.as_mut_ptr() as *mut _,
extraInfo: null_mut(),
}).collect();
let mut args = JavaVMInitArgs {
version: JNI_VERSION_1_6,
nOptions: java_vm_options.len() as _,
options: java_vm_options.as_mut_ptr(),
ignoreUnrecognized: JNI_FALSE,
};
let mut vm = null_mut();
let mut env = null_mut();
let r = unsafe { (self.jvm.JNI_CreateJavaVM)(&mut vm, &mut env, &mut args as *mut _ as *mut _) };
if r == JNI_OK {
Ok(vm)
} else {
Err(JniError(r))
}
}
pub fn get_created_java_vms(&self) -> Result<Vec<*mut JavaVM>, JniError> {
let mut vms = Vec::new();
vms.resize(1, null_mut());
for _try in 0..10 {
let n = vms.len() as _;
let mut nvms = 0;
let r = unsafe { (self.jvm.JNI_GetCreatedJavaVMs)(vms.as_mut_ptr(), n, &mut nvms) };
if r == JNI_OK && nvms <= n {
vms.resize(nvms as _, null_mut());
return Ok(vms);
} else if nvms > n {
vms.resize(nvms as _, null_mut());
continue; } else {
return Err(JniError(r));
}
}
Err(JniError(JNI_ENOMEM))
}
#[cfg_attr(feature = "nightly", doc(cfg(not(target_os = "android"))))] pub fn get_default_java_vm_init_args(&self) -> Result<JavaVMInitArgs, JniError> {
let mut args : JavaVMInitArgs = JavaVMInitArgs { version: 0, nOptions: 0, options: null_mut(), ignoreUnrecognized: JNI_FALSE };
let r = unsafe { (self.jvm.JNI_GetDefaultJavaVMInitArgs)(&mut args as *mut _ as *mut _) };
if r == JNI_OK {
Ok(args)
} else {
Err(JniError(r))
}
}
}