#![doc = include_str!("../Readme.md")]
use std::ffi::c_void;
use std::mem::size_of;
use std::os::raw::*;
use std::io;
use std::path::Path;
use std::ptr::*;
pub type Error = std::io::Error;
pub type Result<T> = std::io::Result<T>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Library(NonNull<c_void>);
unsafe impl Send for Library {}
unsafe impl Sync for Library {}
impl Library {
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
#[cfg(windows)] let handle = {
use std::os::windows::ffi::OsStrExt;
let filename = path.as_os_str().encode_wide().chain([0].iter().copied()).collect::<Vec<u16>>();
unsafe { LoadLibraryW(filename.as_ptr()) }
};
#[cfg(unix)] let handle = {
use std::os::unix::ffi::OsStrExt;
let filename = path.as_os_str().as_bytes().iter().copied().chain([0].iter().copied()).collect::<Vec<u8>>();
let _ = unsafe { dlerror() }; unsafe { dlopen(filename.as_ptr() as _, RTLD_LAZY) }
};
if let Some(handle) = NonNull::new(handle) {
Ok(Self(handle))
} else {
#[cfg(windows)] {
let err = Error::last_os_error();
match err.raw_os_error() {
Some(ERROR_BAD_EXE_FORMAT) => {
Err(io::Error::new(io::ErrorKind::Other, format!(
"Unable to load {path}: ERROR_BAD_EXE_FORMAT (likely tried to load a {that}-bit DLL into this {this}-bit process)",
path = path.display(),
this = if cfg!(target_arch = "x86_64") { "64" } else { "32" },
that = if cfg!(target_arch = "x86_64") { "32" } else { "64" },
)))
},
Some(ERROR_MOD_NOT_FOUND) => {
Err(io::Error::new(io::ErrorKind::NotFound, format!(
"Unable to load {path}: NotFound",
path = path.display(),
)))
},
_ => Err(err)
}
}
#[cfg(unix)] {
Err(io::Error::new(io::ErrorKind::Other, dlerror_string_lossy()))
}
}
}
pub unsafe fn from_ptr(handle: *mut c_void) -> Option<Self> { Some(Self::from_non_null(NonNull::new(handle)?)) }
pub unsafe fn from_non_null(handle: NonNull<c_void>) -> Self { Self(handle) }
pub fn as_ptr(&self) -> *mut c_void { self.0.as_ptr() }
pub fn as_non_null(&self) -> NonNull<c_void> { self.0 }
pub unsafe fn sym<'a, T>(&self, name: impl AsRef<str>) -> io::Result<T> {
let name = name.as_ref();
self.sym_opt(name).ok_or_else(||{
io::Error::new(io::ErrorKind::InvalidInput, format!("Symbol {:?} missing from library", &name[..name.len()-1]))
})
}
pub unsafe fn sym_opt<'a, T>(&self, name: impl AsRef<str>) -> Option<T> {
let name = name.as_ref();
let module = self.as_ptr();
let n = name.len();
assert_eq!(size_of::<T>(), size_of::<*mut c_void>(), "symbol result is not pointer sized!");
assert!(name.ends_with('\0'), "symbol name must end with '\0'");
assert!(!name[..n-1].contains('\0'), "symbol name mustn't contain '\0's, except to terminate the string");
let cname = name.as_ptr() as _;
#[cfg(windows)] let result = GetProcAddress(module, cname);
#[cfg(unix)] let result = dlsym(module, cname);
if result == null_mut() {
None
} else {
Some(std::ptr::read(&result as *const *mut c_void as *const T))
}
}
pub unsafe fn sym_by_ordinal<T>(self, ordinal: u16) -> io::Result<T> {
self.sym_opt_by_ordinal(ordinal).ok_or_else(||{
io::Error::new(io::ErrorKind::InvalidInput, format!("Symbol @{} missing from library", ordinal))
})
}
pub unsafe fn sym_opt_by_ordinal<T>(self, ordinal: u16) -> Option<T> {
assert_eq!(size_of::<T>(), size_of::<*mut c_void>(), "symbol result is not pointer sized!");
#[cfg(windows)] let func = GetProcAddress(self.as_ptr(), ordinal as usize as *const _);
#[cfg(unix)] let func = null_mut::<c_void>();
#[cfg(unix)] let _ = ordinal;
if func.is_null() {
None
} else {
Some(std::mem::transmute_copy::<*mut c_void, T>(&func))
}
}
pub fn has_sym(self, name: impl AsRef<str>) -> bool {
let s : Option<*mut c_void> = unsafe { self.sym_opt(name) };
s.is_some()
}
pub unsafe fn close_unsafe_unsound_possible_noop_do_not_use_in_production(self) -> io::Result<()> {
#[cfg(windows)] match FreeLibrary(self.as_ptr()) {
0 => Err(io::Error::last_os_error()),
_ => Ok(()), }
#[cfg(unix)] match dlclose(self.as_ptr()) {
0 => Ok(()), _ => Err(io::Error::new(io::ErrorKind::Other, dlerror_string_lossy()))
}
}
}
#[cfg(windows)] const ERROR_BAD_EXE_FORMAT : i32 = 0x00C1;
#[cfg(windows)] const ERROR_MOD_NOT_FOUND : i32 = 0x007E;
#[cfg(windows)] extern "system" {
fn GetProcAddress(hModule: *mut c_void, lpProcName: *const c_char) -> *mut c_void;
fn LoadLibraryW(lpFileName: *const u16) -> *mut c_void;
fn FreeLibrary(hModule: *mut c_void) -> u32;
}
#[cfg(unix)] fn dlerror_string_lossy() -> String {
let e = unsafe { dlerror() };
if e.is_null() { String::new() } else { unsafe { std::ffi::CStr::from_ptr(e) }.to_string_lossy().into() }
}
#[cfg(unix)] const RTLD_LAZY : c_int = 1;
#[cfg(unix)] extern "C" {
fn dlopen(filename: *const c_char, flags: c_int) -> *mut c_void;
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
fn dlerror() -> *const c_char;
fn dlclose(handle: *mut c_void) -> c_int;
}