use std::ffi::{CStr, OsStr, c_void};
use std::os::windows::ffi::OsStrExt;
use std::ptr::NonNull;
use windows_sys::Win32::Foundation::{FreeLibrary, GetLastError, HMODULE};
use windows_sys::Win32::System::Diagnostics::Debug::{
FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW,
SEM_FAILCRITICALERRORS, SetThreadErrorMode,
};
use windows_sys::Win32::System::LibraryLoader::{
GetModuleHandleExW, GetProcAddress, LOAD_LIBRARY_SEARCH_APPLICATION_DIR,
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
LOAD_LIBRARY_SEARCH_SYSTEM32, LOAD_LIBRARY_SEARCH_USER_DIRS, LOAD_WITH_ALTERED_SEARCH_PATH,
LoadLibraryExW,
};
use super::Lookup;
use crate::{Error, Library, Result};
pub(crate) const DEFAULT_FLAGS: u32 = 0;
#[derive(Debug, Clone, Default)]
pub struct OpenOptions {
flags: u32,
}
impl OpenOptions {
pub fn new() -> Self {
Self {
flags: DEFAULT_FLAGS,
}
}
fn set(&mut self, flag: u32, on: bool) -> &mut Self {
if on {
self.flags |= flag;
} else {
self.flags &= !flag;
}
self
}
pub fn search_dll_load_dir(&mut self, on: bool) -> &mut Self {
self.set(LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, on)
}
pub fn search_application_dir(&mut self, on: bool) -> &mut Self {
self.set(LOAD_LIBRARY_SEARCH_APPLICATION_DIR, on)
}
pub fn search_user_dirs(&mut self, on: bool) -> &mut Self {
self.set(LOAD_LIBRARY_SEARCH_USER_DIRS, on)
}
pub fn search_system32(&mut self, on: bool) -> &mut Self {
self.set(LOAD_LIBRARY_SEARCH_SYSTEM32, on)
}
pub fn search_default_dirs(&mut self, on: bool) -> &mut Self {
self.set(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, on)
}
pub fn altered_search_path(&mut self, on: bool) -> &mut Self {
self.set(LOAD_WITH_ALTERED_SEARCH_PATH, on)
}
pub fn custom_flags(&mut self, flags: u32) -> &mut Self {
self.flags = flags;
self
}
pub unsafe fn open(&self, path: impl AsRef<OsStr>) -> Result<Library> {
unsafe { open(path.as_ref(), self.flags) }.map(Library::from_handle)
}
}
pub trait LibraryExt: Sized {
fn open_already_loaded(name: impl AsRef<OsStr>) -> Result<Self>;
}
impl LibraryExt for Library {
fn open_already_loaded(name: impl AsRef<OsStr>) -> Result<Self> {
let wide = to_wide(name.as_ref())?;
module_handle(wide.as_ptr()).map_or_else(
|| {
Err(Error::Open {
path: name.as_ref().to_owned(),
message: last_error(),
})
},
|h| Ok(Library::from_handle(h)),
)
}
}
#[derive(Debug)]
pub(crate) struct Handle(NonNull<c_void>);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
fn last_error() -> String {
let code = unsafe { GetLastError() };
let mut buf = [0u16; 512];
let len = unsafe {
FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
std::ptr::null(),
code,
0,
buf.as_mut_ptr(),
buf.len() as u32,
std::ptr::null(),
)
};
if len == 0 {
return format!("error code {code}");
}
String::from_utf16_lossy(&buf[..len as usize])
.trim_end()
.to_owned()
}
fn to_wide(s: &OsStr) -> Result<Vec<u16>> {
let wide: Vec<u16> = s.encode_wide().chain(Some(0)).collect();
match wide[..wide.len() - 1].iter().position(|&c| c == 0) {
Some(position) => Err(Error::InteriorNul { position }),
None => Ok(wide),
}
}
fn module_handle(name: *const u16) -> Option<Handle> {
let mut module: HMODULE = std::ptr::null_mut();
let ok = unsafe { GetModuleHandleExW(0, name, &mut module) };
if ok == 0 {
return None;
}
NonNull::new(module).map(Handle)
}
pub(crate) unsafe fn open(path: &OsStr, flags: u32) -> Result<Handle> {
let wide = to_wide(path)?;
let mut previous = 0;
let restore = unsafe { SetThreadErrorMode(SEM_FAILCRITICALERRORS, &mut previous) } != 0;
let handle = unsafe { LoadLibraryExW(wide.as_ptr(), std::ptr::null_mut(), flags) };
let result = NonNull::new(handle).map(Handle).ok_or_else(|| Error::Open {
path: path.to_owned(),
message: last_error(),
});
if restore {
unsafe { SetThreadErrorMode(previous, std::ptr::null_mut()) };
}
result
}
pub(crate) fn this() -> Result<Handle> {
module_handle(std::ptr::null()).ok_or_else(|| Error::Open {
path: Default::default(),
message: last_error(),
})
}
pub(crate) fn lookup(handle: &Handle, name: &CStr) -> Lookup {
match unsafe { GetProcAddress(handle.0.as_ptr(), name.as_ptr().cast()) } {
Some(f) => Lookup::Found(f as *mut c_void),
None => Lookup::NotFound(last_error()),
}
}
pub(crate) fn close(handle: Handle) -> Result<()> {
if unsafe { FreeLibrary(handle.0.as_ptr()) } != 0 {
Ok(())
} else {
Err(Error::Close {
message: last_error(),
})
}
}