rhai_dylib/loader/libloading.rs
1//! # dylib loader.
2//!
3//! The [`Libloading`] loader enables you to expend functionality of a [`rhai::Engine`] via dynamic libraries using [`libloading`](https://github.com/nagisa/rust_libloading).
4//!
5//! You need to declare the entrypoint function of your module, following the [`Entrypoint`] prototype.
6//! The name of the function must be the same as [`MODULE_ENTRYPOINT`].
7//!
8//! ```rust,ignore
9//! fn module_entrypoint() -> rhai::Shared<rhai::Module> {
10//! // ...
11//! }
12//! ```
13//!
14//! You can easily, for example, implement and export your module using Rhai's [plugin modules](https://rhai.rs/book/plugins/module.html).
15//!
16//! ```rust,ignore
17//! use rhai::plugin::*;
18//!
19//! // Use the `export_module` macro to generate your api.
20//! #[export_module]
21//! mod my_api {
22//! pub fn get_num() -> i64 {
23//! 3
24//! }
25//! pub fn print_stuff() {
26//! println!("Hello World!");
27//! }
28//! }
29//!
30//! // The entrypoint function of your module.
31//! // `extern "C"` can be omitted if you are using the `rust` feature.
32//! #[no_mangle]
33//! extern "C" fn module_entrypoint() -> rhai::Shared<rhai::Module> {
34//! // Build your module.
35//! rhai::exported_module!(my_api).into()
36//! }
37//! ```
38
39use super::Loader;
40
41/// Entrypoint prototype for a Rhai module "constructor".
42pub type Entrypoint = fn() -> rhai::Shared<rhai::Module>;
43/// The name of the function that will be called to update the [`rhai::Engine`].
44pub const MODULE_ENTRYPOINT: &str = "module_entrypoint";
45
46/// Loading dynamic libraries using the [`libloading`](https://github.com/nagisa/rust_libloading) crate.
47///
48/// # Example
49///
50/// ```rust,ignore
51/// // Create your dynamic library loader & rhai engine.
52/// let mut loader = rhai_dylib::loader::libloading::Libloading::new();
53/// let mut engine = rhai::Engine::new();
54///
55/// // `my_first_module` library exposes the `print_first` function.
56/// loader.load("my_first_module.so", &mut engine).expect("failed to load library 1");
57/// // `my_second_module` library exposes the `print_second` function.
58/// loader.load("my_second_module.so", &mut engine).expect("failed to load library 2");
59///
60/// // functions are now registered in the engine and can be called !
61/// engine.run(r"
62/// print_first();
63/// print_second();
64/// ");
65/// ```
66pub struct Libloading {
67 /// Libraries loaded in memory.
68 libraries: Vec<libloading::Library>,
69}
70
71impl Default for Libloading {
72 /// Create a new instance of the loader.
73 fn default() -> Self {
74 Self { libraries: vec![] }
75 }
76}
77
78impl Libloading {
79 /// Create a new instance of the loader.
80 #[must_use]
81 pub fn new() -> Self {
82 Self::default()
83 }
84}
85
86impl Loader for Libloading {
87 /// Load a rhai module from a dynamic library.
88 fn load(
89 &mut self,
90 path: impl AsRef<std::path::Path>,
91 ) -> Result<rhai::Shared<rhai::Module>, Box<rhai::EvalAltResult>> {
92 let library = unsafe {
93 #[cfg(target_os = "linux")]
94 {
95 // Workaround for a crash on library unloading on linux: https://github.com/nagisa/rust_libloading/issues/5#issuecomment-244195096
96 libloading::os::unix::Library::open(
97 Some(path.as_ref()),
98 // Load library with `RTLD_NOW | RTLD_NODELETE` to fix SIGSEGV.
99 0x2 | 0x1000,
100 )
101 .map(libloading::Library::from)
102 }
103
104 #[cfg(any(target_os = "macos", target_os = "windows"))]
105 {
106 libloading::Library::new(path.as_ref())
107 }
108 }
109 .map_err(|error| {
110 rhai::EvalAltResult::ErrorInModule(
111 path.as_ref()
112 .to_str()
113 .map_or(String::default(), std::string::ToString::to_string),
114 error.to_string().into(),
115 rhai::Position::NONE,
116 )
117 })?;
118
119 self.libraries.push(library);
120 let library = self.libraries.last().expect("library just got inserted");
121
122 let module_entrypoint = unsafe { library.get::<Entrypoint>(MODULE_ENTRYPOINT.as_bytes()) }
123 .map_err(|error| {
124 rhai::EvalAltResult::ErrorInModule(
125 path.as_ref()
126 .to_str()
127 .map_or(String::default(), std::string::ToString::to_string),
128 error.to_string().into(),
129 rhai::Position::NONE,
130 )
131 })?;
132
133 Ok(module_entrypoint())
134 }
135}