use std::{
ffi::{CStr, CString, OsStr, c_char, c_void},
path::Path,
};
#[derive(Debug, Clone)]
pub enum DylibError {
OpenError(String),
SymbolError(String),
CloseError(String),
}
impl std::fmt::Display for DylibError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DylibError::OpenError(msg) => write!(f, "Failed to open dynamic library: {}", msg),
DylibError::SymbolError(msg) => write!(f, "Failed to find symbol: {}", msg),
DylibError::CloseError(msg) => write!(f, "Failed to close dynamic library: {}", msg),
}
}
}
impl std::error::Error for DylibError {}
pub struct DynamicLibrary {
handle: *mut c_void,
}
pub struct Symbol<T> {
ptr: *mut c_void,
_marker: std::marker::PhantomData<T>,
}
impl<T> Symbol<T> {
pub fn as_ptr(&self) -> *mut c_void {
self.ptr
}
}
impl<T> std::ops::Deref for Symbol<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*(self.ptr as *const T) }
}
}
impl DynamicLibrary {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, DylibError> {
let path_str = path.as_ref().to_string_lossy();
#[cfg(windows)]
{
use windows_sys::Win32::{
Foundation::{GetLastError, HMODULE},
System::LibraryLoader::LoadLibraryA,
};
let c_path = CString::new(path_str.as_bytes()).map_err(|e| DylibError::OpenError(e.to_string()))?;
let handle = unsafe { LoadLibraryA(c_path.as_ptr() as *const u8) };
if handle.is_null() {
let error_code = unsafe { GetLastError() };
return Err(DylibError::OpenError(format!("Windows error code: {}", error_code)));
}
Ok(Self { handle: handle as *mut c_void })
}
#[cfg(unix)]
{
use libc::{RTLD_LOCAL, RTLD_NOW, dlopen};
let c_path = CString::new(path_str.as_bytes()).map_err(|e| DylibError::OpenError(e.to_string()))?;
let handle = unsafe { dlopen(c_path.as_ptr(), RTLD_NOW | RTLD_LOCAL) };
if handle.is_null() {
let error = unsafe {
let err_ptr = libc::dlerror();
if err_ptr.is_null() {
"Unknown error".to_string()
}
else {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
}
};
return Err(DylibError::OpenError(error));
}
Ok(Self { handle })
}
}
pub fn get<T>(&self, name: &str) -> Result<Symbol<T>, DylibError> {
#[cfg(windows)]
{
use windows_sys::Win32::{Foundation::HMODULE, System::LibraryLoader::GetProcAddress};
let c_name = CString::new(name).map_err(|e| DylibError::SymbolError(e.to_string()))?;
let ptr = unsafe { GetProcAddress(self.handle as HMODULE, c_name.as_ptr() as *const u8) };
if ptr.is_none() {
return Err(DylibError::SymbolError(format!("Symbol '{}' not found", name)));
}
Ok(Symbol { ptr: ptr.unwrap() as *mut c_void, _marker: std::marker::PhantomData })
}
#[cfg(unix)]
{
use libc::dlsym;
let c_name = CString::new(name).map_err(|e| DylibError::SymbolError(e.to_string()))?;
let ptr = unsafe { dlsym(self.handle, c_name.as_ptr()) };
if ptr.is_null() {
let error = unsafe {
let err_ptr = libc::dlerror();
if err_ptr.is_null() {
format!("Symbol '{}' not found", name)
}
else {
format!("Symbol '{}' not found: {}", name, CStr::from_ptr(err_ptr).to_string_lossy())
}
};
return Err(DylibError::SymbolError(error));
}
Ok(Symbol { ptr, _marker: std::marker::PhantomData })
}
}
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
}
impl Drop for DynamicLibrary {
fn drop(&mut self) {
if !self.handle.is_null() {
#[cfg(windows)]
{
use windows_sys::Win32::Foundation::{FreeLibrary, HMODULE};
unsafe {
FreeLibrary(self.handle as HMODULE);
}
}
#[cfg(unix)]
{
use libc::dlclose;
unsafe {
dlclose(self.handle);
}
}
}
}
}
unsafe impl Send for DynamicLibrary {}
unsafe impl Sync for DynamicLibrary {}
pub fn get_dylib_extension() -> &'static str {
#[cfg(target_os = "windows")]
{
"dll"
}
#[cfg(target_os = "macos")]
{
"dylib"
}
#[cfg(target_os = "linux")]
{
"so"
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
{
"so"
}
}
pub fn get_dylib_prefix() -> &'static str {
#[cfg(target_os = "windows")]
{
""
}
#[cfg(not(target_os = "windows"))]
{
"lib"
}
}
pub fn build_dylib_name(name: &str) -> String {
format!("{}{}.{}", get_dylib_prefix(), name, get_dylib_extension())
}