use std::ffi::c_void;
use std::fmt;
use std::path::Path;
#[derive(Debug)]
pub struct Error(String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for Error {}
pub struct Library {
handle: *mut c_void,
}
unsafe impl Send for Library {}
unsafe impl Sync for Library {}
impl Library {
pub unsafe fn load(path: &Path) -> Result<Self, Error> {
imp::load(path).map(|handle| Self { handle })
}
pub unsafe fn symbol<F: Copy>(&self, name: &str) -> Result<F, Error> {
assert_eq!(
std::mem::size_of::<F>(),
std::mem::size_of::<*mut c_void>(),
"symbol type must be pointer-sized (e.g. a function pointer)",
);
let ptr = imp::symbol(self.handle, name)?;
Ok(std::mem::transmute_copy::<*mut c_void, F>(&ptr))
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe { imp::close(self.handle) };
}
}
#[cfg(unix)]
mod imp {
use super::Error;
use std::ffi::{c_void, CString};
use std::path::Path;
extern "C" {
fn dlopen(filename: *const i8, flag: i32) -> *mut c_void;
fn dlsym(handle: *mut c_void, symbol: *const i8) -> *mut c_void;
fn dlclose(handle: *mut c_void) -> i32;
fn dlerror() -> *const i8;
}
const RTLD_NOW: i32 = 2;
const RTLD_LOCAL: i32 = 0;
fn last_error(fallback: &str) -> Error {
unsafe {
let ptr = dlerror();
if ptr.is_null() {
return Error(fallback.to_owned());
}
let mut len = 0;
while *ptr.add(len) != 0 {
len += 1;
}
let bytes = std::slice::from_raw_parts(ptr.cast::<u8>(), len);
Error(String::from_utf8_lossy(bytes).into_owned())
}
}
pub(super) fn load(path: &Path) -> Result<*mut c_void, Error> {
let path_str = path.to_str().ok_or_else(|| {
Error(format!(
"library path is not valid UTF-8: {}",
path.display()
))
})?;
let c_path = CString::new(path_str)
.map_err(|_| Error("library path contains an interior NUL byte".to_owned()))?;
unsafe { dlerror() };
let handle = unsafe { dlopen(c_path.as_ptr(), RTLD_NOW | RTLD_LOCAL) };
if handle.is_null() {
return Err(last_error("dlopen failed"));
}
Ok(handle)
}
pub(super) fn symbol(handle: *mut c_void, name: &str) -> Result<*mut c_void, Error> {
let c_name = CString::new(name)
.map_err(|_| Error(format!("symbol name contains a NUL byte: {name}")))?;
unsafe { dlerror() };
let ptr = unsafe { dlsym(handle, c_name.as_ptr()) };
if ptr.is_null() {
let err = last_error("symbol not found");
return Err(Error(format!("failed to load symbol '{name}': {err}")));
}
Ok(ptr)
}
pub(super) unsafe fn close(handle: *mut c_void) {
let _ = dlclose(handle);
}
}
#[cfg(windows)]
mod imp {
use super::Error;
use std::ffi::{c_void, CString};
use std::path::Path;
#[link(name = "kernel32")]
extern "system" {
fn LoadLibraryW(filename: *const u16) -> *mut c_void;
fn GetProcAddress(module: *mut c_void, name: *const i8) -> *mut c_void;
fn FreeLibrary(module: *mut c_void) -> i32;
fn GetLastError() -> u32;
}
fn last_error(prefix: &str) -> Error {
let code = unsafe { GetLastError() };
Error(format!("{prefix} (Windows error {code})"))
}
pub(super) fn load(path: &Path) -> Result<*mut c_void, Error> {
use std::os::windows::ffi::OsStrExt;
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
wide.push(0);
let handle = unsafe { LoadLibraryW(wide.as_ptr()) };
if handle.is_null() {
return Err(last_error(&format!(
"LoadLibraryW failed for {}",
path.display()
)));
}
Ok(handle)
}
pub(super) fn symbol(handle: *mut c_void, name: &str) -> Result<*mut c_void, Error> {
let c_name = CString::new(name)
.map_err(|_| Error(format!("symbol name contains a NUL byte: {name}")))?;
let ptr = unsafe { GetProcAddress(handle, c_name.as_ptr()) };
if ptr.is_null() {
return Err(last_error(&format!("failed to load symbol '{name}'")));
}
Ok(ptr)
}
pub(super) unsafe fn close(handle: *mut c_void) {
let _ = FreeLibrary(handle);
}
}