relib_host/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::{ffi::OsStr, path::Path};

use libloading::Symbol;

use relib_internal_shared::Str;

mod errors;
pub use errors::LoadError;

#[cfg(feature = "unloading")]
mod unloading;

mod module;
pub use module::Module;
mod helpers;
use helpers::{next_module_id, open_library};
mod leak_library;
pub mod exports_types;
pub use exports_types::{ModuleExportsForHost, InitImports, ModuleValue};

/// # Example
/// ```
/// let path_to_dylib = if cfg!(target_os = "linux") {
///   "target/debug/libmodule.so"
/// } else {
///   "target/debug/module.dll"
/// };
///
/// // `()` means empty imports and exports, module doesn't import or export anything
/// let module = relib_host::load_module::<()>(path_to_dylib, ()).unwrap();
///
/// // main function is unsafe to call (as well as any other module export) because these pre-conditions are not checked by relib:
/// // - Returned value must be actually `R` at runtime. For example if you called this function with type bool but module returns i32, UB will occur.
/// // - Type of return value must be FFI-safe.
/// let returned_value = unsafe { module.call_main::<()>() };
///
/// // if module panics while executing any export it returns None
/// // (panic will be printed by module)
/// if returned_value.is_none() {
///   println!("module panicked");
/// }
/// ```
pub fn load_module<E: ModuleExportsForHost>(
  path: impl AsRef<OsStr>,
  init_imports: impl InitImports,
) -> Result<Module<E>, crate::LoadError> {
  let path = Path::new(path.as_ref());

  #[cfg(target_os = "linux")]
  {
    use helpers::linux::is_library_loaded;

    if is_library_loaded(path) {
      return Err(LoadError::ModuleAlreadyLoaded);
    }
  }

  let library = open_library(path)?;

  let module_comp_info = unsafe {
    let compiled_with: Symbol<*const Str> = library.get(b"__RELIB__CRATE_COMPILATION_INFO__\0")?;
    let compiled_with: &Str = &**compiled_with;
    compiled_with.to_string()
  };

  let host_comp_info = relib_internal_crate_compilation_info::get!();
  if module_comp_info != host_comp_info {
    return Err(LoadError::ModuleCompilationMismatch {
      module: module_comp_info,
      host: host_comp_info.to_owned(),
    });
  }

  let module_id = next_module_id();

  #[cfg(feature = "unloading")]
  let internal_exports = {
    unloading::init_internal_imports(&library);
    unloading::module_allocs::add_module(module_id);

    let internal_exports = unloading::InternalModuleExports::new(&library);
    unsafe {
      internal_exports.init(thread_id::get(), module_id);
    }
    internal_exports
  };

  let pub_exports = E::new(&library);
  init_imports.init(&library);

  let module = Module::new(
    module_id,
    library,
    pub_exports,
    #[cfg(feature = "unloading")]
    (internal_exports, path.to_owned()),
  );
  Ok(module)
}

// TODO: fix it
#[cfg(all(target_os = "windows", feature = "unloading"))]
#[expect(clippy::missing_safety_doc)]
pub unsafe fn __suppress_unused_warning_for_linux_only_exports(
  exports: unloading::InternalModuleExports,
) {
  exports.spawned_threads_count();
}