Skip to main content

miniquad_ply/native/
module.rs

1#[derive(Debug, PartialEq, Eq, Clone)]
2pub enum Error {
3    DlOpenError(String),
4    DlSymError(String),
5}
6
7impl Display for Error {
8    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9        match self {
10            Self::DlOpenError(msg) => write!(f, "Shared library open error:\n{msg}"),
11            Self::DlSymError(msg) => write!(f, "Shared library symlink error:\n{msg}"),
12        }
13    }
14}
15
16#[cfg(any(target_os = "linux", target_os = "android"))]
17pub mod linux {
18    use super::Error;
19    use libc::{dlclose, dlopen, dlsym, RTLD_LAZY, RTLD_LOCAL};
20    use std::{
21        ffi::{c_void, CString},
22        ptr::NonNull,
23    };
24
25    pub struct Module(NonNull<c_void>);
26
27    impl Module {
28        pub fn load(path: &str) -> Result<Self, Error> {
29            let cpath = CString::new(path).unwrap();
30            let module = unsafe { dlopen(cpath.as_ptr(), RTLD_LAZY | RTLD_LOCAL) };
31            if module.is_null() {
32                Err(Error::DlOpenError(path.to_string()))
33            } else {
34                Ok(Module(unsafe { NonNull::new_unchecked(module) }))
35            }
36        }
37
38        pub fn get_symbol<F: Sized>(&self, name: &str) -> Result<F, Error> {
39            let cname = CString::new(name).unwrap();
40            let symbol = unsafe { dlsym(self.0.as_ptr(), cname.as_ptr()) };
41            if symbol.is_null() {
42                return Err(Error::DlSymError(name.to_string()));
43            }
44            Ok(unsafe { std::mem::transmute_copy::<_, F>(&symbol) })
45        }
46    }
47
48    impl Drop for Module {
49        fn drop(&mut self) {
50            unsafe { dlclose(self.0.as_ptr()) };
51        }
52    }
53}
54
55#[cfg(target_os = "windows")]
56mod windows {
57    use super::Error;
58    use winapi::{
59        shared::minwindef::HINSTANCE,
60        um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA},
61    };
62
63    pub struct Module(pub HINSTANCE);
64
65    impl Module {
66        pub fn load(path: &str) -> Result<Self, Error> {
67            let cpath = std::ffi::CString::new(path).unwrap();
68            let library = unsafe { LoadLibraryA(cpath.as_ptr()) };
69            if library.is_null() {
70                return Err(Error::DlOpenError(path.to_string()));
71            }
72            Ok(Self(library))
73        }
74        pub fn get_symbol<F: Sized>(&self, name: &str) -> Result<F, Error> {
75            let cname = std::ffi::CString::new(name).unwrap();
76            let proc = unsafe { GetProcAddress(self.0, cname.as_ptr() as *const _) };
77            if proc.is_null() {
78                return Err(Error::DlSymError(name.to_string()));
79            }
80            Ok(unsafe { std::mem::transmute_copy(&proc) })
81        }
82    }
83
84    impl Drop for Module {
85        fn drop(&mut self) {
86            unsafe { FreeLibrary(self.0) };
87        }
88    }
89}
90
91use std::fmt::Display;
92
93#[cfg(any(target_os = "linux", target_os = "android"))]
94pub use linux::*;
95
96#[cfg(target_os = "windows")]
97pub use windows::Module;
98
99#[cfg(any(target_os = "linux", target_os = "android"))]
100#[macro_export]
101macro_rules! declare_module {
102    ($name:ident,
103    $path:literal$(, $fallback:literal)*$(,)?
104    ...
105    // static
106    $($s_vis:vis $s_name:ident: $s_type:ty,)*
107    ...
108    // function
109    $($f_vis:vis fn $f_name:ident($($f_arg:ty),*$(,)?)$( -> $f_ret:ty )?,)*
110    ...
111    // function with variadic arguments
112    $($v_vis:vis fn $v_name:ident($($v_arg:ty),*, ...)$( -> $v_ret:ty )?,)*
113    ...
114    // extra field (should be `Default` so `try_load` can construct it)
115    $($vis:vis $field:ident: $field_ty:ty,)*) => {
116        #[derive(Clone)]
117        pub struct $name {
118            _module: std::rc::Rc<$crate::native::module::Module>,
119            $($s_vis $s_name: $s_type,)*
120            $($f_vis $f_name: unsafe extern "C" fn ($($f_arg),*)$( -> $f_ret)?,)*
121            $($v_vis $v_name: unsafe extern "C" fn ($($v_arg),*, ...)$( -> $v_ret)?,)*
122            $($vis $field: $field_ty)*
123        }
124        impl $name {
125            pub fn try_load() -> Result<Self, $crate::native::module::Error> {
126                $crate::native::module::Module::load($path)$(.or_else(|_| $crate::native::module::Module::load($fallback)))*
127                .map(|module|
128                    $name {
129                        $($s_name: module.get_symbol::<$s_type>(stringify!($s_name)).unwrap(),)*
130                        $($f_name: module.get_symbol::<unsafe extern "C" fn ($($f_arg),*)$( -> $f_ret)?>(stringify!($f_name)).unwrap(),)*
131                        $($v_name: module.get_symbol::<unsafe extern "C" fn ($($v_arg),*, ...)$( -> $v_ret)?>(stringify!($v_name)).unwrap(),)*
132                        $($field: Default::default(),)*
133                        _module: module.into(),
134                    }
135                )
136            }
137        }
138    }
139}