dyn_loader/helpers.rs
1//! Shared `DynLib` wrapper and helpers used by both [`crate::dyn_mod`]
2//! (Rust fat-pointer bridge) and [`crate::cdyn`] (COM-style vtable loading).
3
4use std::path::Path;
5use std::sync::Arc;
6
7use anyhow::{Context, Result};
8use libloading::Library;
9
10/// Type of the entry point function that dyn plugins must export.
11pub type PluginEntryPointRaw = unsafe extern "C" fn() -> std::ffi::c_void;
12
13// ---------------------------------------------------------------------------
14// DynLib — Arc-shared dynamic library
15// ---------------------------------------------------------------------------
16
17#[derive(Clone)]
18pub struct DynLib {
19 library: Arc<Library>,
20 path: std::path::PathBuf,
21}
22
23impl DynLib {
24 /// Load a dynamic library from the given path.
25 ///
26 /// # Safety
27 ///
28 /// The target file must be a valid dynamic library for the current process.
29 pub unsafe fn load(path: &Path) -> Result<Self> {
30 let library = unsafe { Library::new(path) }
31 .with_context(|| format!("failed to load dynamic library: {}", path.display()))?;
32 Ok(Self {
33 library: Arc::new(library),
34 path: path.to_path_buf(),
35 })
36 }
37 pub fn path(&self) -> &Path {
38 &self.path
39 }
40
41 /// Load a symbol from the library.
42 ///
43 /// # Safety
44 ///
45 /// The caller must ensure `T` matches the actual exported symbol type.
46 pub unsafe fn symbol<T: Copy>(&self, name: &[u8]) -> Result<T> {
47 let sym = unsafe { self.library.get::<T>(name) }.with_context(|| {
48 format!(
49 "symbol '{}' not found in {}",
50 display_symbol(name),
51 self.path.display()
52 )
53 })?;
54 Ok(*sym)
55 }
56
57 /// Try to load a symbol; returns `None` if not found.
58 ///
59 /// # Safety
60 ///
61 /// The caller must ensure `T` matches the actual exported symbol type.
62 pub unsafe fn try_symbol<T: Copy>(&self, name: &[u8]) -> Option<T> {
63 unsafe { self.library.get::<T>(name) }.ok().map(|s| *s)
64 }
65}
66
67// ---------------------------------------------------------------------------
68// Helpers
69// ---------------------------------------------------------------------------
70
71pub(crate) fn display_symbol(symbol: &[u8]) -> String {
72 let end = symbol.iter().position(|&b| b == 0).unwrap_or(symbol.len());
73 String::from_utf8_lossy(&symbol[..end]).into_owned()
74}
75
76/// Quick check: does the file look like a plugin with the given entry symbol?
77///
78/// # Safety
79///
80/// Probes an arbitrary dynamic library for a specific exported symbol.
81pub unsafe fn looks_like_plugin(path: &Path, entry_symbol: &[u8]) -> bool {
82 match unsafe { DynLib::load(path) } {
83 Ok(lib) => unsafe { lib.try_symbol::<PluginEntryPointRaw>(entry_symbol) }.is_some(),
84 Err(_) => false,
85 }
86}