Skip to main content

jni_simple/
linking.rs

1use crate::{JNI_OK, JNIEnv, JNIInvPtr, JavaVM, JavaVMInitArgs, JavaVMOption, jint};
2
3use alloc::ffi::CString;
4use alloc::vec::Vec;
5use core::ffi::{c_char, c_void};
6use core::ptr::null_mut;
7use sync_ptr::SyncMutPtr;
8
9#[cfg(feature = "loadjvm")]
10use alloc::boxed::Box;
11#[cfg(feature = "loadjvm")]
12use alloc::string::String;
13#[cfg(all(feature = "loadjvm", not(feature = "dynlink")))]
14use alloc::string::ToString;
15#[cfg(feature = "loadjvm")]
16use core::error::Error;
17#[cfg(feature = "loadjvm")]
18use core::fmt::{Display, Formatter};
19
20#[cfg(not(feature = "dynlink"))]
21use crate::jsize;
22
23#[cfg(not(feature = "dynlink"))]
24use sync_ptr::{SyncFnPtr, sync_fn_ptr_from_addr};
25
26/// Module that contains the dll/so imports from the JVM.
27/// This module should only be used when writing a library that is loaded by the JVM
28/// using `System.load` or `System.loadLibrary`
29#[cfg(feature = "dynlink")]
30mod dynlink {
31    use crate::{JNIEnv, JNIInvPtr, JavaVMInitArgs, jint, jsize};
32
33    unsafe extern "system" {
34        pub fn JNI_CreateJavaVM(invoker: *mut JNIInvPtr, env: *mut JNIEnv, initargs: *mut JavaVMInitArgs) -> jint;
35        pub fn JNI_GetCreatedJavaVMs(array: *mut JNIInvPtr, len: jsize, out: *mut jsize) -> jint;
36    }
37}
38
39/// type signature for the extern fn in the jvm
40#[cfg(not(feature = "dynlink"))]
41type JNI_CreateJavaVM = unsafe extern "C" fn(*mut JNIInvPtr, *mut JNIEnv, *mut JavaVMInitArgs) -> jint;
42
43/// type signature for the extern fn in the jvm
44#[cfg(not(feature = "dynlink"))]
45type JNI_GetCreatedJavaVMs = unsafe extern "C" fn(*mut JNIInvPtr, jsize, *mut jsize) -> jint;
46
47/// Data holder for the raw JVM function pointers.
48#[cfg(not(feature = "dynlink"))]
49#[derive(Debug, Copy, Clone)]
50pub struct JNIDynamicLink {
51    /// raw function ptr to `JNI_CreateJavaVM`
52    JNI_CreateJavaVM: SyncFnPtr<JNI_CreateJavaVM>,
53    /// raw function ptr to `JNI_GetCreatedJavaVMs`
54    JNI_GetCreatedJavaVMs: SyncFnPtr<JNI_GetCreatedJavaVMs>,
55}
56
57#[cfg(not(feature = "dynlink"))]
58impl JNIDynamicLink {
59    /// Constructor with the two pointers
60    /// # Panics
61    /// If any of the pointers are null.
62    #[must_use]
63    pub fn new(JNI_CreateJavaVM: *const c_void, JNI_GetCreatedJavaVMs: *const c_void) -> Self {
64        assert!(!JNI_GetCreatedJavaVMs.is_null(), "JNI_GetCreatedJavaVMs is null");
65        assert!(!JNI_CreateJavaVM.is_null(), "JNI_CreateJavaVM is null");
66
67        unsafe {
68            Self {
69                JNI_CreateJavaVM: sync_fn_ptr_from_addr!(JNI_CreateJavaVM, JNI_CreateJavaVM),
70                JNI_GetCreatedJavaVMs: sync_fn_ptr_from_addr!(JNI_GetCreatedJavaVMs, JNI_GetCreatedJavaVMs),
71            }
72        }
73    }
74
75    /// Get the `JNI_GetCreatedJavaVMs` function pointer
76    #[must_use]
77    pub fn JNI_CreateJavaVM(&self) -> JNI_CreateJavaVM {
78        self.JNI_CreateJavaVM.unwrap()
79    }
80
81    /// Get the `JNI_GetCreatedJavaVMs` function pointer
82    #[must_use]
83    pub fn JNI_GetCreatedJavaVMs(&self) -> JNI_GetCreatedJavaVMs {
84        self.JNI_GetCreatedJavaVMs.unwrap()
85    }
86}
87
88#[cfg(feature = "std")]
89#[cfg(not(feature = "dynlink"))]
90/// Standard library-based synchronization to prevent loading the jvm multiple times.
91mod std_link {
92    use crate::linking::JNIDynamicLink;
93
94    /// Static state
95    static LINK: std::sync::RwLock<Option<JNIDynamicLink>> = std::sync::RwLock::new(None);
96
97    /// Writeable exclusive access to the static state
98    pub fn link_write() -> std::sync::RwLockWriteGuard<'static, Option<JNIDynamicLink>> {
99        LINK.write().unwrap_or_else(|e| {
100            LINK.clear_poison();
101            e.into_inner()
102        })
103    }
104
105    /// Readable shared access to the static state
106    pub fn link_read() -> std::sync::RwLockReadGuard<'static, Option<JNIDynamicLink>> {
107        LINK.read().unwrap_or_else(|e| {
108            LINK.clear_poison();
109            e.into_inner()
110        })
111    }
112}
113
114#[cfg(feature = "std")]
115#[cfg(not(feature = "dynlink"))]
116pub use std_link::{link_read, link_write};
117
118#[cfg(not(feature = "std"))]
119#[cfg(not(feature = "dynlink"))]
120/// Spin-lock-based synchronization to prevent loading the jvm multiple times.
121mod spin_link {
122    // The reason why I implemented this myself instead of using the spin crate is
123    // because it would add spin as a dependency when compiling for std.
124    // If I make spin optional, then selecting default-features=false won't compile unless
125    // the user selects the "spin" feature manually.
126
127    use crate::linking::JNIDynamicLink;
128    use core::cell::UnsafeCell;
129    use core::ops::{Deref, DerefMut};
130    use core::sync::atomic::AtomicUsize;
131    use core::sync::atomic::Ordering::SeqCst;
132
133    /// Wrapper for `UnsafeCell` that can be put into static.
134    struct UCellWrapper(UnsafeCell<Option<JNIDynamicLink>>);
135    unsafe impl Send for UCellWrapper {}
136    unsafe impl Sync for UCellWrapper {}
137
138    /// `usize::MAX` / 2 or larger means the writer has it locked
139    /// 0 is unlocked
140    /// smaller than `usize::MAX` / 2: number of readers locked.
141    static LOCK: AtomicUsize = AtomicUsize::new(0);
142
143    /// The static state.
144    static LINK: UCellWrapper = UCellWrapper(UnsafeCell::new(None));
145
146    /// See LOCK above
147    const USIZE_HALF: usize = usize::MAX / 2;
148
149    /// Immutable guard to the global state
150    pub struct SpinLockGuard;
151    impl Deref for SpinLockGuard {
152        type Target = Option<JNIDynamicLink>;
153
154        fn deref(&self) -> &Self::Target {
155            unsafe { &*LINK.0.get() }
156        }
157    }
158
159    impl Drop for SpinLockGuard {
160        fn drop(&mut self) {
161            let r = LOCK.fetch_sub(1, SeqCst);
162            debug_assert!(r != 0);
163        }
164    }
165
166    /// Mutable guard to the global state
167    pub struct SpinLockGuardMut;
168
169    impl Deref for SpinLockGuardMut {
170        type Target = Option<JNIDynamicLink>;
171
172        fn deref(&self) -> &Self::Target {
173            unsafe { &*LINK.0.get() }
174        }
175    }
176
177    impl DerefMut for SpinLockGuardMut {
178        fn deref_mut(&mut self) -> &mut Self::Target {
179            unsafe { &mut *LINK.0.get() }
180        }
181    }
182
183    impl Drop for SpinLockGuardMut {
184        fn drop(&mut self) {
185            let r = LOCK.fetch_sub(USIZE_HALF, SeqCst);
186            debug_assert!(r >= USIZE_HALF);
187        }
188    }
189
190    /// Writeable exclusive access to the static state
191    pub fn link_write() -> SpinLockGuardMut {
192        loop {
193            if LOCK.compare_exchange(0, USIZE_HALF, SeqCst, SeqCst).is_ok() {
194                return SpinLockGuardMut;
195            }
196
197            core::hint::spin_loop();
198        }
199    }
200
201    /// Readable shared access to the static state
202    pub fn link_read() -> SpinLockGuard {
203        loop {
204            // Safety: if this overflows, we are boned,
205            // but I don't think any os can spawn usize::threads,
206            // so we are pretty safe.
207            if LOCK.fetch_add(1, SeqCst) < USIZE_HALF - 1 {
208                return SpinLockGuard;
209            }
210            LOCK.fetch_sub(1, SeqCst);
211
212            core::hint::spin_loop();
213        }
214    }
215}
216
217#[cfg(not(feature = "std"))]
218#[cfg(not(feature = "dynlink"))]
219pub use spin_link::{link_read, link_write};
220
221///
222/// Call this function to initialize the dynamic linking to the jvm to use the provided function pointers to
223/// create the jvm.
224///
225/// If this function is called more than once, then it is a noop, since it is not possible to create
226/// more than one jvm per process.
227///
228/// # Panics
229/// If any of the argument pointers are null.
230///
231/// # Returns
232/// true if the call initialized the dynamic link, false if it was already initialized.
233///
234#[cfg(not(feature = "dynlink"))]
235#[must_use]
236pub fn init_dynamic_link(JNI_CreateJavaVM: *const c_void, JNI_GetCreatedJavaVMs: *const c_void) -> bool {
237    let mut guard = link_write();
238    if guard.is_some() {
239        return false;
240    }
241
242    *guard = Some(JNIDynamicLink::new(JNI_CreateJavaVM, JNI_GetCreatedJavaVMs));
243    true
244}
245
246///
247/// Call this function to initialize the dynamic linking to the jvm to use the provided function pointers to
248/// create the jvm.
249///
250/// If this function is called more than once, then it is a noop, since it is not possible to create
251/// more than one jvm per process.
252///
253/// # Returns
254/// true if the call initialized the dynamic link, false if it was already initialized.
255///
256#[cfg(feature = "dynlink")]
257#[allow(clippy::missing_const_for_fn)]
258#[must_use]
259pub fn init_dynamic_link(_: *const c_void, _: *const c_void) -> bool {
260    //NOOP, because the dynamic linker already must have preloaded the jvm for linking to succeed.
261    false
262}
263
264///
265/// Returns true if the jvm was loaded by either calling `load_jvm_from_library` or `init_dynamic_link`.
266///
267#[cfg(not(feature = "dynlink"))]
268#[must_use]
269pub fn is_jvm_loaded() -> bool {
270    link_read().is_some()
271}
272
273/// Returns the static dynamic link or panic
274/// # Panics
275/// if the dynamic link was not initialized.
276#[cfg(not(feature = "dynlink"))]
277fn get_link() -> JNIDynamicLink {
278    link_read().expect("jni_simple::init_dynamic_link not called")
279}
280
281///
282/// Returns true if the jvm was loaded by either calling `load_jvm_from_library` or `init_dynamic_link`.
283///
284#[cfg(feature = "dynlink")]
285#[must_use]
286#[allow(clippy::missing_const_for_fn)]
287pub fn is_jvm_loaded() -> bool {
288    true
289}
290
291#[cfg(feature = "loadjvm")]
292#[non_exhaustive]
293#[derive(Debug)]
294pub enum LoadFromLibraryError {
295    /// The dynamic linker has already loaded the jvm.
296    AlreadyLoaded,
297    /// The dynamic linker failed to load the jvm shared object.
298    LoadingSharedObjectFailed {
299        /// relative path to the shared object.
300        path: String,
301        /// platform-specific error
302        error: Box<dyn Error>,
303    },
304    /// The dynamic linker could not find the `JNI_CreateJavaVM` symbol in the shared object.
305    JNICreateJavaVmNotFound {
306        /// relative path to the shared object.
307        path: String,
308        /// platform-specific error
309        error: Box<dyn Error>,
310    },
311    /// The dynamic linker could not find the `JNI_GetCreatedJavaVMs` symbol in the shared object.
312    JNIGetCreatedJavaVMsNotFound {
313        /// relative path to the shared object.
314        path: String,
315        /// platform-specific error
316        error: Box<dyn Error>,
317    },
318}
319
320#[cfg(feature = "loadjvm")]
321impl Display for LoadFromLibraryError {
322    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
323        match self {
324            Self::AlreadyLoaded => f.write_str("The dynamic linker has already loaded the jvm."),
325            Self::LoadingSharedObjectFailed { .. } => f.write_str("The dynamic linker failed to load the jvm shared object."),
326            Self::JNICreateJavaVmNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_CreateJavaVM symbol in the shared object."),
327            Self::JNIGetCreatedJavaVMsNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_GetCreatedJavaVMs symbol in the shared object."),
328        }
329    }
330}
331
332#[cfg(feature = "loadjvm")]
333impl Error for LoadFromLibraryError {
334    fn source(&self) -> Option<&(dyn Error + 'static)> {
335        match self {
336            Self::AlreadyLoaded => None,
337            Self::LoadingSharedObjectFailed { error, .. } | Self::JNICreateJavaVmNotFound { error, .. } | Self::JNIGetCreatedJavaVMsNotFound { error, .. } => Some(&**error),
338        }
339    }
340}
341
342/// Provided for backwards compile-compat with ? operator,
343/// all errors used to be just Strings,
344/// which was perhaps not the wisest choice.
345#[cfg(feature = "loadjvm")]
346impl From<LoadFromLibraryError> for String {
347    fn from(value: LoadFromLibraryError) -> Self {
348        alloc::format!("{value}")
349    }
350}
351
352///
353/// Convenience method to load the jvm from a path to libjvm.so or jvm.dll.
354///
355/// On success this method does NOT close the handle to the shared object.
356/// This is usually fine because unloading the jvm is not supported anyway.
357/// If you do not desire this then use `init_dynamic_link`.
358///
359/// # Errors
360/// if loading the library fails without crashing the process, then a String describing the reason why is returned as an error.
361///
362/// # Safety
363/// The Safety of this fn depends on the shared object that will be loaded as a result of this call.
364///
365#[cfg(feature = "loadjvm")]
366#[cfg(not(feature = "dynlink"))]
367pub unsafe fn load_jvm_from_library(path: &str) -> Result<(), LoadFromLibraryError> {
368    let mut guard = link_write();
369    if guard.is_some() {
370        drop(guard);
371        return Err(LoadFromLibraryError::AlreadyLoaded);
372    }
373
374    unsafe {
375        let lib = libloading::Library::new(path).map_err(|e| LoadFromLibraryError::LoadingSharedObjectFailed {
376            path: path.to_string(),
377            error: Box::new(e),
378        })?;
379
380        //NetBSD: https://mail-index.netbsd.org/netbsd-bugs/2025/11/23/msg090714.html
381        //Apple: https://github.com/nagisa/rust_libloading/issues/5
382        //We cannot "unload" libraries on error cases on these platforms as the unloading
383        //Is likely to seg fault and crash the process.
384        //In the success case, this doesn't matter because we never unload the jvm shared object once loaded anyway.
385        #[cfg(any(target_os = "netbsd", target_vendor = "apple"))]
386        let lib = core::mem::ManuallyDrop::new(lib);
387
388        let JNI_CreateJavaVM_ptr = lib
389            .get::<JNI_CreateJavaVM>(b"JNI_CreateJavaVM\0")
390            .map_err(|e| LoadFromLibraryError::JNICreateJavaVmNotFound {
391                path: path.to_string(),
392                error: Box::new(e),
393            })?
394            .try_as_raw_ptr()
395            .ok_or_else(|| LoadFromLibraryError::JNICreateJavaVmNotFound {
396                path: path.to_string(),
397                error: Box::new(libloading::Error::DlSymUnknown),
398            })?;
399
400        if JNI_CreateJavaVM_ptr.is_null() {
401            return Err(LoadFromLibraryError::JNICreateJavaVmNotFound {
402                path: path.to_string(),
403                error: Box::new(libloading::Error::DlSymUnknown),
404            });
405        }
406
407        let JNI_GetCreatedJavaVMs_ptr = lib
408            .get::<JNI_GetCreatedJavaVMs>(b"JNI_GetCreatedJavaVMs\0")
409            .map_err(|e| LoadFromLibraryError::JNIGetCreatedJavaVMsNotFound {
410                path: path.to_string(),
411                error: Box::new(e),
412            })?
413            .try_as_raw_ptr()
414            .ok_or_else(|| LoadFromLibraryError::JNIGetCreatedJavaVMsNotFound {
415                path: path.to_string(),
416                error: Box::new(libloading::Error::DlSymUnknown),
417            })?;
418
419        if JNI_GetCreatedJavaVMs_ptr.is_null() {
420            return Err(LoadFromLibraryError::JNIGetCreatedJavaVMsNotFound {
421                path: path.to_string(),
422                error: Box::new(libloading::Error::DlSymUnknown),
423            });
424        }
425
426        //We are good to go!
427        #[cfg(not(any(target_os = "netbsd", target_vendor = "apple")))] //see above for why.
428        core::mem::forget(lib);
429        *guard = Some(JNIDynamicLink::new(JNI_CreateJavaVM_ptr, JNI_GetCreatedJavaVMs_ptr));
430        drop(guard);
431    }
432
433    Ok(())
434}
435
436///
437/// Convenience method to load the jvm from a path to libjvm.so, jvm.dll or libjvm.dylib.
438///
439/// On success this method does NOT close the handle to the shared object.
440/// This is usually fine because unloading the jvm is not supported anyway.
441/// If you do not desire this then use `init_dynamic_link`.
442///
443/// # Errors
444/// if loading the library fails without crashing the process then a String describing the reason why is returned as an error.
445///
446/// # Safety
447/// The Safety of this fn depends on the shared object that will be loaded as a result of this call.
448///
449#[cfg(feature = "loadjvm")]
450#[cfg(feature = "dynlink")]
451pub unsafe fn load_jvm_from_library(_: &str) -> Result<(), LoadFromLibraryError> {
452    Err(LoadFromLibraryError::AlreadyLoaded)
453}
454
455#[cfg(feature = "loadjvm")]
456#[cfg(feature = "std")]
457#[non_exhaustive]
458#[derive(Debug)]
459pub enum LoadFromJavaHomeError {
460    /// The dynamic linker has already loaded the jvm.
461    AlreadyLoaded,
462    /// The dynamic linker failed to load the jvm shared object.
463    LoadingSharedObjectFailed {
464        /// relative path to the shared object.
465        path: String,
466        /// platform-specific error
467        error: Box<dyn Error>,
468    },
469    /// The dynamic linker could not find the `JNI_CreateJavaVM` symbol in the shared object.
470    JNICreateJavaVmNotFound {
471        /// relative path to the shared object.
472        path: String,
473        /// platform-specific error
474        error: Box<dyn Error>,
475    },
476    /// The dynamic linker could not find the `JNI_GetCreatedJavaVMs` symbol in the shared object.
477    JNIGetCreatedJavaVMsNotFound {
478        /// relative path to the shared object.
479        path: String,
480        /// platform-specific error
481        error: Box<dyn Error>,
482    },
483    /// The layout of the java installation was not recognized.
484    UnknownJavaHomeLayout,
485    /// I/O Error while determining the layout of the java installation.
486    IOError(std::io::Error),
487    /// The environment variable `JAVA_HOME` is invalid
488    EnvironmentVariableError(std::env::VarError),
489}
490
491#[cfg(feature = "loadjvm")]
492#[cfg(feature = "std")]
493impl From<LoadFromJavaHomeFolderError> for LoadFromJavaHomeError {
494    fn from(value: LoadFromJavaHomeFolderError) -> Self {
495        match value {
496            LoadFromJavaHomeFolderError::AlreadyLoaded => Self::AlreadyLoaded,
497            LoadFromJavaHomeFolderError::LoadingSharedObjectFailed { path, error } => Self::LoadingSharedObjectFailed { path, error },
498            LoadFromJavaHomeFolderError::JNICreateJavaVmNotFound { path, error } => Self::JNICreateJavaVmNotFound { path, error },
499            LoadFromJavaHomeFolderError::JNIGetCreatedJavaVMsNotFound { path, error } => Self::JNIGetCreatedJavaVMsNotFound { path, error },
500            LoadFromJavaHomeFolderError::UnknownJavaHomeLayout => Self::UnknownJavaHomeLayout,
501            LoadFromJavaHomeFolderError::IOError(e) => Self::IOError(e),
502        }
503    }
504}
505
506#[cfg(feature = "loadjvm")]
507#[cfg(feature = "std")]
508impl From<LoadFromLibraryError> for LoadFromJavaHomeError {
509    fn from(value: LoadFromLibraryError) -> Self {
510        match value {
511            LoadFromLibraryError::AlreadyLoaded => Self::AlreadyLoaded,
512            LoadFromLibraryError::LoadingSharedObjectFailed { path, error } => Self::LoadingSharedObjectFailed { path, error },
513            LoadFromLibraryError::JNICreateJavaVmNotFound { path, error } => Self::JNICreateJavaVmNotFound { path, error },
514            LoadFromLibraryError::JNIGetCreatedJavaVMsNotFound { path, error } => Self::JNIGetCreatedJavaVMsNotFound { path, error },
515        }
516    }
517}
518
519#[cfg(feature = "loadjvm")]
520#[cfg(feature = "std")]
521impl Display for LoadFromJavaHomeError {
522    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
523        match self {
524            Self::AlreadyLoaded => f.write_str("The dynamic linker has already loaded the jvm."),
525            Self::LoadingSharedObjectFailed { .. } => f.write_str("The dynamic linker failed to load the jvm shared object."),
526            Self::JNICreateJavaVmNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_CreateJavaVM symbol in the shared object."),
527            Self::JNIGetCreatedJavaVMsNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_GetCreatedJavaVMs symbol in the shared object."),
528            Self::UnknownJavaHomeLayout => f.write_str("The layout of the java installation was not recognized."),
529            Self::IOError(_) => f.write_str("I/O Error while determining the layout of the java installation."),
530            Self::EnvironmentVariableError(_) => f.write_str("The environment variable JAVA_HOME is invalid"),
531        }
532    }
533}
534
535#[cfg(feature = "loadjvm")]
536#[cfg(feature = "std")]
537impl Error for LoadFromJavaHomeError {
538    fn source(&self) -> Option<&(dyn Error + 'static)> {
539        match self {
540            Self::AlreadyLoaded | Self::UnknownJavaHomeLayout => None,
541            Self::LoadingSharedObjectFailed { error, .. } | Self::JNICreateJavaVmNotFound { error, .. } | Self::JNIGetCreatedJavaVMsNotFound { error, .. } => Some(&**error),
542            Self::IOError(e) => Some(e),
543            Self::EnvironmentVariableError(e) => Some(e),
544        }
545    }
546}
547
548/// Provided for backwards compile-compat with ? operator,
549/// all errors used to be just Strings,
550/// which was perhaps not the wisest choice.
551#[cfg(feature = "loadjvm")]
552#[cfg(feature = "std")]
553impl From<LoadFromJavaHomeError> for String {
554    fn from(value: LoadFromJavaHomeError) -> Self {
555        alloc::format!("{value}")
556    }
557}
558
559/// Convenience method to load the jvm from the `JAVA_HOME` environment variable
560/// that is commonly set on Windows by End-User Java Setups,
561/// or on linux by distribution package installers.
562///
563/// # Errors
564/// If `JAVA_HOME` is not set or doesn't point to a known layout of a JVM installation or cant be read
565/// then this function returns an error.
566///
567/// # Safety
568/// The Safety of this fn depends on the shared object that will be loaded as a result of this call.
569///
570#[cfg(feature = "loadjvm")]
571#[cfg(feature = "std")]
572pub unsafe fn load_jvm_from_java_home() -> Result<(), LoadFromJavaHomeError> {
573    let java_home = std::env::var("JAVA_HOME").map_err(LoadFromJavaHomeError::EnvironmentVariableError)?;
574
575    unsafe {
576        load_jvm_from_java_home_folder(&java_home)?;
577    }
578    Ok(())
579}
580
581///TODO DOCU
582#[cfg(feature = "loadjvm")]
583#[cfg(feature = "std")]
584#[non_exhaustive]
585#[derive(Debug)]
586pub enum LoadFromJavaHomeFolderError {
587    /// The dynamic linker has already loaded the jvm.
588    AlreadyLoaded,
589    /// The dynamic linker failed to load the jvm shared object.
590    LoadingSharedObjectFailed {
591        /// relative path to the shared object.
592        path: String,
593        /// platform-specific error
594        error: Box<dyn Error>,
595    },
596    /// The dynamic linker could not find the `JNI_CreateJavaVM` symbol in the shared object.
597    JNICreateJavaVmNotFound {
598        /// relative path to the shared object.
599        path: String,
600        /// platform-specific error
601        error: Box<dyn Error>,
602    },
603    /// The dynamic linker could not find the `JNI_GetCreatedJavaVMs` symbol in the shared object.
604    JNIGetCreatedJavaVMsNotFound {
605        /// relative path to the shared object.
606        path: String,
607        /// platform-specific error
608        error: Box<dyn Error>,
609    },
610    /// The layout of the java installation was not recognized.
611    UnknownJavaHomeLayout,
612    /// I/O Error while determining the layout of the java installation.
613    IOError(std::io::Error),
614}
615
616#[cfg(feature = "loadjvm")]
617#[cfg(feature = "std")]
618impl Display for LoadFromJavaHomeFolderError {
619    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
620        match self {
621            Self::AlreadyLoaded => f.write_str("The dynamic linker has already loaded the jvm."),
622            Self::LoadingSharedObjectFailed { .. } => f.write_str("The dynamic linker failed to load the jvm shared object."),
623            Self::JNICreateJavaVmNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_CreateJavaVM symbol in the shared object."),
624            Self::JNIGetCreatedJavaVMsNotFound { .. } => f.write_str("The dynamic linker could not find the JNI_GetCreatedJavaVMs symbol in the shared object."),
625            Self::UnknownJavaHomeLayout => f.write_str("The layout of the java installation was not recognized."),
626            Self::IOError(_) => f.write_str("I/O Error while determining the layout of the java installation."),
627        }
628    }
629}
630
631#[cfg(feature = "loadjvm")]
632#[cfg(feature = "std")]
633impl From<LoadFromLibraryError> for LoadFromJavaHomeFolderError {
634    fn from(value: LoadFromLibraryError) -> Self {
635        match value {
636            LoadFromLibraryError::AlreadyLoaded => Self::AlreadyLoaded,
637            LoadFromLibraryError::LoadingSharedObjectFailed { path, error } => Self::LoadingSharedObjectFailed { path, error },
638            LoadFromLibraryError::JNICreateJavaVmNotFound { path, error } => Self::JNICreateJavaVmNotFound { path, error },
639            LoadFromLibraryError::JNIGetCreatedJavaVMsNotFound { path, error } => Self::JNIGetCreatedJavaVMsNotFound { path, error },
640        }
641    }
642}
643
644/// Provided for backwards compile-compat with ? operator,
645/// all errors used to be just Strings,
646/// which was perhaps not the wisest choice.
647#[cfg(feature = "loadjvm")]
648#[cfg(feature = "std")]
649impl From<LoadFromJavaHomeFolderError> for String {
650    fn from(value: LoadFromJavaHomeFolderError) -> Self {
651        alloc::format!("{value}")
652    }
653}
654
655/// Convenience method to load the jvm from a given path to a java installation.
656/// Info: The `java_home` parameter should refer to a path of a folder, which directly contains the "bin" or "jre" folder.
657///
658/// # Errors
659/// If `java_home` doesn't refer to a known layout of a JVM installation or cant be read
660/// then this function returns an error.
661///
662/// # Safety
663/// The Safety of this fn depends on the shared object that will be loaded as a result of this call.
664#[cfg(feature = "loadjvm")]
665#[cfg(feature = "std")]
666pub unsafe fn load_jvm_from_java_home_folder(java_home: &str) -> Result<(), LoadFromJavaHomeFolderError> {
667    ///All (most) jvm layouts that I am aware of on windows+linux+macos.
668    static COMMON_LIBJVM_PATHS: &[&[&str]] = &[
669        #[cfg(all(unix, not(target_vendor = "apple")))]
670        &["lib", "server", "libjvm.so"], //UNIX JAVA 11+
671        #[cfg(all(unix, not(target_vendor = "apple")))]
672        &["jre", "lib", "amd64", "server", "libjvm.so"], //UNIX JDK JAVA <= 8 amd64
673        #[cfg(all(unix, not(target_vendor = "apple")))]
674        &["lib", "amd64", "server", "libjvm.so"], //UNIX JRE JAVA <= 8 amd64
675        #[cfg(all(unix, not(target_vendor = "apple")))]
676        &["jre", "lib", "aarch32", "server", "libjvm.so"], //UNIX JDK JAVA <= 8 arm 32
677        #[cfg(all(unix, not(target_vendor = "apple")))]
678        &["lib", "aarch32", "server", "libjvm.so"], //UNIX JRE JAVA <= 8 arm 32
679        #[cfg(all(unix, not(target_vendor = "apple")))]
680        &["jre", "lib", "aarch64", "server", "libjvm.so"], //UNIX JDK JAVA <= 8 arm 64
681        #[cfg(all(unix, not(target_vendor = "apple")))]
682        &["lib", "aarch64", "server", "libjvm.so"], //UNIX JRE JAVA <= 8 arm 64
683        //
684        #[cfg(windows)]
685        &["jre", "bin", "server", "jvm.dll"], //WINDOWS JDK <= 8
686        #[cfg(windows)]
687        &["bin", "server", "jvm.dll"], //WINDOWS JRE <= 8 AND WINDOWS JDK/JRE 11+
688        //
689        #[cfg(target_vendor = "apple")]
690        &["jre", "lib", "server", "libjvm.dylib"], //MACOS Java <= 8
691        #[cfg(target_vendor = "apple")]
692        &["Contents", "Home", "jre", "lib", "server", "libjvm.dylib"], //MACOS Java <= 8
693        #[cfg(target_vendor = "apple")]
694        &["lib", "server", "libjvm.dylib"], //MACOS Java 11+
695        #[cfg(target_vendor = "apple")]
696        &["Contents", "Home", "lib", "server", "libjvm.dylib"], //MACOS Java 11+
697    ];
698
699    for parts in COMMON_LIBJVM_PATHS {
700        let mut buf = std::path::PathBuf::from(java_home);
701        for part in *parts {
702            buf.push(part);
703        }
704
705        if buf.try_exists().map_err(LoadFromJavaHomeFolderError::IOError)? {
706            let full_path = buf
707                .to_str()
708                .ok_or_else(|| LoadFromJavaHomeFolderError::IOError(std::io::Error::other("Failed to concatenate JAVA_HOME library path")))?;
709
710            unsafe {
711                load_jvm_from_library(full_path)?;
712            }
713
714            return Ok(());
715        }
716    }
717
718    Err(LoadFromJavaHomeFolderError::UnknownJavaHomeLayout)
719}
720
721///
722/// Returns the created `JavaVMs` in the given `vms` slice.
723/// All remaining elements in the slice are set to None.
724/// The count of returned `JavaVMs` is returned in the result.
725///
726/// If the given slice is smaller than the amount of created `JavaVMs` then
727/// this function does not error and simply returns the amount
728/// of space in the slice that would have been needed.
729///
730/// If this function returns an Err then the slice is untouched.
731///
732/// # Note
733/// This will probably only ever return 1 (or 0) `JavaVM`s according to Oracle Documentation
734/// as the hotspot jvm does not support more than 1 JVM per process.
735///
736/// # Errors
737/// JNI implementation specific error constants like `JNI_EINVAL`
738///
739/// # Panics
740/// Will panic if the JVM shared library has not been loaded yet.
741/// If the JVM's `JNI_GetCreatedJavaVMs` method returns unexpected values
742///
743/// # Safety
744/// The Safety of this fn is implementation dependant.
745///
746pub unsafe fn JNI_GetCreatedJavaVMs(vms: &mut [Option<JavaVM>]) -> Result<usize, jint> {
747    #[cfg(not(feature = "dynlink"))]
748    let link = get_link().JNI_GetCreatedJavaVMs();
749    #[cfg(feature = "dynlink")]
750    let link = dynlink::JNI_GetCreatedJavaVMs;
751
752    //NOTE: Oracle spec says this will only ever yield 1 JVM.
753    //I will worry about this when it actually becomes a problem
754    let mut buf: [JNIInvPtr; 64] = [SyncMutPtr::null(); 64];
755    let mut count: jint = 0;
756    let res = unsafe { link(buf.as_mut_ptr(), 64, &raw mut count) };
757    if res != JNI_OK {
758        return Err(res);
759    }
760
761    let count = usize::try_from(count).expect("JNI_GetCreatedJavaVMs did set count to < 0");
762
763    for (i, env) in buf.into_iter().enumerate().take(count) {
764        assert!(!env.is_null(), "JNI_GetCreatedJavaVMs VM #{i} is null! count is {count}");
765    }
766
767    for (i, target) in vms.iter_mut().enumerate() {
768        if i >= count {
769            *target = None;
770            continue;
771        }
772
773        *target = Some(JavaVM { vtable: buf[i] });
774    }
775
776    Ok(count)
777}
778///
779/// Returns the first created `JavaVM` or None in the result.
780///
781/// Usually there is only 1 created or 0 created `JavaVM`'s in any given process.
782/// This function acts as a convenience function that only returns the first and probably only `JavaVM`.
783///
784/// # Errors
785/// JNI implementation specific error constants like `JNI_EINVAL`
786///
787/// # Panics
788/// Will panic if the JVM shared library has not been loaded yet.
789/// If the JVM's `JNI_GetCreatedJavaVMs` method returns unexpected values
790///
791/// # Safety
792/// The Safety of this fn is implementation dependant.
793///
794pub unsafe fn JNI_GetCreatedJavaVMs_first() -> Result<Option<JavaVM>, jint> {
795    unsafe {
796        let mut vm = [None];
797        _ = JNI_GetCreatedJavaVMs(vm.as_mut())?;
798        Ok(vm[0])
799    }
800}
801
802///
803/// Directly calls `JNI_CreateJavaVM` with the provided arguments.
804///
805/// # Errors
806/// JNI implementation specific error constants like `JNI_EINVAL`
807///
808/// # Panics
809/// Will panic if the JVM shared library has not been loaded yet.
810/// Will panic if the JVM shared library retruned unexpected values.
811///
812/// # Safety
813/// The Safety of this fn is implementation dependant.
814/// On Hotspot JVM's this fn cannot be called successfully more than once.
815/// Subsequent calls are undefined behaviour.
816///
817pub unsafe fn JNI_CreateJavaVM(arguments: *mut JavaVMInitArgs) -> Result<(JavaVM, JNIEnv), jint> {
818    #[cfg(feature = "asserts")]
819    {
820        assert!(!arguments.is_null(), "JNI_CreateJavaVM arguments must not be null");
821    }
822
823    #[cfg(not(feature = "dynlink"))]
824    let link = get_link().JNI_CreateJavaVM();
825    #[cfg(feature = "dynlink")]
826    let link = dynlink::JNI_CreateJavaVM;
827
828    let mut jvm: JNIInvPtr = SyncMutPtr::null();
829    let mut env: JNIEnv = JNIEnv { vtable: null_mut() };
830
831    let res = unsafe { link(&raw mut jvm, &raw mut env, arguments) };
832    if res != JNI_OK {
833        return Err(res);
834    }
835
836    assert!(!jvm.is_null(), "JNI_CreateJavaVM returned JNI_OK but the JavaVM pointer is null");
837
838    assert!(!env.vtable.is_null(), "JNI_CreateJavaVM returned JNI_OK but the JNIEnv pointer is null");
839
840    Ok((JavaVM { vtable: jvm }, env))
841}
842
843///
844/// Convenience function to call `JNI_CreateJavaVM` with a simple list of String arguments.
845///
846/// These arguments are almost identical to the command line arguments used to start the jvm with the java binary.
847/// Some options differ slightly. Consult the JNI Invocation API documentation for more information.
848///
849/// # Errors
850/// JNI implementation specific error constants like `JNI_EINVAL`
851///
852/// # Panics
853/// Will panic if the JVM shared library has not been loaded yet.
854/// Will panic if more than `jsize::MAX` arguments are passed to the vm. (The JVM itself is likely to just die earlier)
855/// If any argument contains a 0 byte in the string.
856///
857/// # Safety
858/// The Safety of this fn is implementation dependant.
859/// On Hotspot JVM's this fn cannot be called successfully more than once.
860/// Subsequent calls are undefined behaviour.
861///
862/// # Example
863/// ```rust
864/// use std::ptr::null_mut;
865/// use jni_simple::*;
866///
867///
868/// //This example fn is roughly equivalent to "java -Xint -Xmx1G -Djava.class.path={absolute_path_to_jar_file} {main_class}" on the command line.
869/// unsafe fn launch_jvm(absolute_path_to_jar_file: &str, main_class: &str) -> ! {
870///     #[cfg(all(feature = "loadjvm", feature = "std"))] //Only needed due to doctest!
871///     load_jvm_from_java_home().expect("Failed to load jvm");
872///
873///     let (vm, env) = JNI_CreateJavaVM_with_string_args(JNI_VERSION_1_8, &[
874///          "-Xint".to_string(),
875///          "-Xmx1G".to_string(),
876///          format!("-Djava.class.path={absolute_path_to_jar_file}")
877///     ], false).expect("Failed to start jvm");
878///
879///     let main_class = env.FindClass(main_class);
880///     if env.ExceptionCheck() {
881///         //Main class not found
882///         env.ExceptionDescribe();
883///         return std::process::exit(-1);
884///     }
885///
886///     let main_method = env.GetStaticMethodID(main_class, "main","([Ljava/lang/String)V");
887///     if env.ExceptionCheck() {
888///         //no static main(String[] args) method in the main class.
889///         env.ExceptionDescribe();
890///         return std::process::exit(-1);
891///     }
892///
893///     let string_class = env.FindClass("java/lang/String");
894///     if env.ExceptionCheck() {
895///         //Unlikely, java.lang.String not found.
896///         env.ExceptionDescribe();
897///         return std::process::exit(-1);
898///     }
899///
900///     let main_method_string_parameter_array = env.NewObjectArray(0, string_class, null_mut());
901///      if env.ExceptionCheck() {
902///         //Unlikely jvm ran out of memory when creating "new String[0];"
903///         env.ExceptionDescribe();
904///         return std::process::exit(-1);
905///     }
906///
907///     env.CallStaticVoidMethod1(main_class, main_method, main_method_string_parameter_array);
908///     if env.ExceptionCheck() {
909///         //Main method threw an exception
910///         env.ExceptionDescribe();
911///         return std::process::exit(-1);
912///     }
913///
914///     //Block until all non deamon java threads the main method has started are done.
915///     vm.DestroyJavaVM();
916///
917///     //Exit the process with success.
918///     std::process::exit(0)
919/// }
920/// ```
921///
922pub unsafe fn JNI_CreateJavaVM_with_string_args<T: AsRef<str>>(version: jint, arguments: &[T], ignore_unrecognized_options: bool) -> Result<(JavaVM, JNIEnv), jint> {
923    unsafe {
924        /// inner helper struct to ensure that the `CStrings` are free'd in any case.
925        struct DropGuard(*mut c_char);
926        impl Drop for DropGuard {
927            fn drop(&mut self) {
928                unsafe {
929                    _ = CString::from_raw(self.0);
930                }
931            }
932        }
933
934        let mut vm_args: Vec<JavaVMOption> = Vec::with_capacity(arguments.len());
935        let mut dealloc_list = Vec::with_capacity(arguments.len());
936        for arg in arguments {
937            let jvm_arg = CString::new(arg.as_ref()).expect("Argument contains 0 byte").into_raw();
938            dealloc_list.push(DropGuard(jvm_arg));
939
940            vm_args.push(JavaVMOption {
941                optionString: jvm_arg,
942                extraInfo: null_mut(),
943            });
944        }
945
946        let mut args = JavaVMInitArgs {
947            version,
948            nOptions: i32::try_from(vm_args.len()).expect("Too many arguments"),
949            options: vm_args.as_mut_ptr(),
950            ignoreUnrecognized: u8::from(ignore_unrecognized_options),
951        };
952
953        let result = JNI_CreateJavaVM(&raw mut args);
954        drop(dealloc_list);
955        result
956    }
957}