From 4010b1baf7fdeec1d6988d5bd42f6c3022093535 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 14 May 2023 14:39:12 -0700 Subject: [PATCH 01/21] refactored attribute macro small simplification to lazyfn error handling --- Cargo.toml | 4 ++-- README.md | 6 +++--- dylink_macro/Cargo.toml | 4 ++-- dylink_macro/src/lib.rs | 36 +++++++++++++++++------------------- src/lazyfn.rs | 12 +++++------- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6aa077a..dee78ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "dylink" -version = "0.5.0" +version = "0.5.1" authors = ["Jonathan Thomason"] edition = "2021" license = "MIT OR Apache-2.0" keywords = ["ffi"] description = "Run-time dynamic linking loader utilities" repository = "https://github.com/Razordor/dylink.git" -publish = true +publish = false [workspace] members = ["dylink_macro"] diff --git a/README.md b/README.md index f55ac45..fd2faa0 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Related links: Dylink has been implemented for all major platforms aside from WASM, but has only been locally tested on Windows and Linux. -| Win64 | Linux | MacOS | Unix(other) | WASM | -|:-----:|:-----:|:--------:|:-----------:|------| -| YES | YES | Untested | Untested | NO | +| Windows | Linux | MacOS | WASM | +|:-------:|:-----:|:--------:|------| +| YES | YES | Untested | NO | ## Usage diff --git a/dylink_macro/Cargo.toml b/dylink_macro/Cargo.toml index 51c70cf..cb89595 100644 --- a/dylink_macro/Cargo.toml +++ b/dylink_macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dylink_macro" -version = "0.6.0" +version = "0.6.1" authors = ["Jonathan Thomason"] edition = "2021" readme = "README.md" @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" keywords = ["ffi", "macro", "attribute"] description = "Run-time dynamic linking loader attribute" repository = "https://github.com/Razordor/dylink.git" -publish = true +publish = false [lib] proc-macro = true diff --git a/dylink_macro/src/lib.rs b/dylink_macro/src/lib.rs index 144bce8..f269416 100644 --- a/dylink_macro/src/lib.rs +++ b/dylink_macro/src/lib.rs @@ -9,38 +9,36 @@ use quote::*; use syn::{parse::Parser, punctuated::Punctuated, spanned::Spanned, Expr, Token}; use attr_data::*; +use syn::ForeignItem; #[proc_macro_attribute] pub fn dylink(args: TokenStream1, input: TokenStream1) -> TokenStream1 { - let args = TokenStream2::from(args); - let input = TokenStream2::from(input); - let foreign_mod = syn::parse2::(input).expect("failed to parse"); + let foreign_mod = syn::parse2::(input.into()).expect("failed to parse"); let punct = Parser::parse2( Punctuated::::parse_separated_nonempty, - args, + args.into(), ) .expect("failed to parse"); - let attr_data = match AttrData::try_from(punct) { - Ok(attr) => attr, - Err(e) => { - return syn::Error::into_compile_error(e).into(); - } - }; - let mut ret = TokenStream2::new(); - for item in foreign_mod.items { - use syn::ForeignItem; - let abi = &foreign_mod.abi; - match item { - ForeignItem::Fn(fn_item) => ret.extend(parse_fn(abi, fn_item, &attr_data)), - other => ret.extend(quote!(#abi {#other})), + match AttrData::try_from(punct) { + Ok(attr_data) => { + let abi = &foreign_mod.abi; + foreign_mod + .items + .iter() + .map(|item| match item { + ForeignItem::Fn(fn_item) => parse_fn(abi, fn_item, &attr_data), + other => quote!(#abi {#other}), + }) + .collect::() + .into() } + Err(e) => syn::Error::into_compile_error(e).into(), } - TokenStream1::from(ret) } -fn parse_fn(abi: &syn::Abi, fn_item: syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { +fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { let fn_name = fn_item.sig.ident.to_token_stream(); let abi = abi.into_token_stream(); let vis = fn_item.vis.to_token_stream(); diff --git a/src/lazyfn.rs b/src/lazyfn.rs index 289e121..d503045 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -106,12 +106,10 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { .map_err(|e| errors.push(e)) .ok() }) - .ok_or_else(|| { - let err: String = errors - .iter() - .map(|e| e.to_string() + "\n") - .collect(); - error::DylinkError::ListNotLoaded(err) + .ok_or_else(|| { + error::DylinkError::ListNotLoaded( + errors.iter().map(|e| e.to_string() + "\n").collect(), + ) }) } }; @@ -130,7 +128,7 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { }); // `call_once` is blocking, so `self.status` is read-only // by this point. Race conditions shouldn't occur. - match (*self.status.borrow()).clone() { + match self.status.clone().take() { None => Ok(self.load(Ordering::Acquire)), Some(err) => Err(err), } From 73bbf1ffbe4251bcfbd10e46c7b2b353836662a2 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 18 May 2023 22:28:17 -0700 Subject: [PATCH 02/21] removed preloaded vk functions --- README.md | 2 +- src/lazyfn/loader.rs | 24 +++++------------------- src/vulkan.rs | 3 --- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index fd2faa0..ab72012 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Related links: ## Supported platforms -Dylink has been implemented for all major platforms aside from WASM, but has only been locally tested on Windows and Linux. +Dylink has been implemented for all major platforms aside from WASM, but has only been tested on Windows and Linux. | Windows | Linux | MacOS | WASM | |:-------:|:-----:|:--------:|------| diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index 2d46a1f..b934adc 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -5,25 +5,11 @@ use std::{ffi, mem, sync::RwLock}; use crate::{error::*, vulkan, FnPtr, Result}; pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Result { - let mut maybe_fn = match fn_name.to_bytes() { - b"vkGetInstanceProcAddr" => { - Some(mem::transmute::( - vulkan::vkGetInstanceProcAddr, - )) - } - b"vkGetDeviceProcAddr" => Some(mem::transmute::( - vulkan::vkGetDeviceProcAddr, - )), - _ => None, - }; - maybe_fn = match maybe_fn { - Some(addr) => return Ok(addr), - None => crate::VK_DEVICE - .read() - .expect("failed to get read lock") - .iter() - .find_map(|device| vulkan::vkGetDeviceProcAddr(*device, fn_name.as_ptr() as *const _)), - }; + let mut maybe_fn = crate::VK_DEVICE + .read() + .expect("failed to get read lock") + .iter() + .find_map(|device| vulkan::vkGetDeviceProcAddr(*device, fn_name.as_ptr() as *const _)); maybe_fn = match maybe_fn { Some(addr) => return Ok(addr), None => crate::VK_INSTANCE diff --git a/src/vulkan.rs b/src/vulkan.rs index 6f5d957..58a7dd5 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -53,9 +53,6 @@ extern "system" { #[allow(non_camel_case_types)] pub(crate) type PFN_vkGetDeviceProcAddr = unsafe extern "system" fn(VkDevice, *const ffi::c_char) -> Option; -#[allow(non_camel_case_types)] -pub(crate) type PFN_vkGetInstanceProcAddr = - unsafe extern "system" fn(VkInstance, *const ffi::c_char) -> Option; // vkGetDeviceProcAddr must be implemented manually to avoid recursion #[allow(non_snake_case)] From 8b4a894a3780ce8cae1ca3dce2eab30b108099d8 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 19 May 2023 00:59:05 -0700 Subject: [PATCH 03/21] replaced HashSet with sorted Vec --- Cargo.toml | 3 --- src/lazyfn/loader.rs | 19 ++++++++--------- src/lib.rs | 49 ++++++++++++++++++++++++++++++++------------ src/vulkan.rs | 4 ++-- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dee78ce..7635048 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,6 @@ publish = false [workspace] members = ["dylink_macro"] -[dependencies] -once_cell = "1.16" - [dependencies.dylink_macro] version = "0.6" path="./dylink_macro" diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index b934adc..7fb6867 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -37,20 +37,18 @@ pub(crate) fn general_loader( lib_name: &ffi::CStr, fn_name: &ffi::CStr, ) -> Result { - use std::collections::HashMap; - - use once_cell::sync::Lazy; - - static DLL_DATA: RwLock>> = - RwLock::new(Lazy::new(HashMap::default)); + static DLL_DATA: RwLock> = + RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. let maybe_fn; let read_lock = DLL_DATA.read().unwrap(); - if let Some(handle) = read_lock.get(lib_name) { - maybe_fn = L::load_sym(handle, fn_name); - } else { + match read_lock.binary_search_by_key(&lib_name, |(k, _)| k) { + Ok(index) => { + maybe_fn = L::load_sym(&read_lock[index].1, fn_name) + } + Err(index) => { mem::drop(read_lock); let lib_handle = L::load_lib(lib_name); @@ -64,9 +62,10 @@ pub(crate) fn general_loader( DLL_DATA .write() .unwrap() - .insert(lib_name.to_owned(), lib_handle); + .insert(index, (lib_name.to_owned(), lib_handle)); } } +} match maybe_fn { Some(addr) => Ok(addr), None => Err(DylinkError::FnNotFound( diff --git a/src/lib.rs b/src/lib.rs index e43e643..f570e87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,9 +93,8 @@ //! Unloading a library means not only are all loaded dylink functions invalidated, but functions loaded from **ALL** //! crates in the project are also invalidated, which will immediately lead to segfaults... a lot of them. -use std::{collections::HashSet, sync}; +use std::sync; -use once_cell::sync::Lazy; use std::ffi; mod error; @@ -121,11 +120,11 @@ compile_error!("Dylink Error: Wasm is unsupported."); // These globals are read every time a vulkan function is called for the first time, // which occurs through `LazyFn::link`. -static VK_INSTANCE: sync::RwLock>> = - sync::RwLock::new(Lazy::new(HashSet::new)); +static VK_INSTANCE: sync::RwLock> = + sync::RwLock::new(Vec::new()); -static VK_DEVICE: sync::RwLock>> = - sync::RwLock::new(Lazy::new(HashSet::new)); +static VK_DEVICE: sync::RwLock> = + sync::RwLock::new(Vec::new()); // Used as a placeholder function pointer. This should **NEVER** be called directly, // and promptly cast into the correct function pointer type. @@ -145,7 +144,7 @@ pub(crate) type Result = std::result::Result; pub struct Global; impl Global { // This is safe since vulkan will just discard garbage values - /// Adds an instance to the internal HashSet. + /// Adds an instance to the internal Vec. /// /// Returns whether the instance was newly inserted. That is: /// @@ -156,7 +155,13 @@ impl Global { pub fn insert_instance(&self, instance: vulkan::VkInstance) -> bool { //println!("insert_instance called!"); let mut write_lock = VK_INSTANCE.write().unwrap(); - write_lock.insert(instance) + match write_lock.binary_search(&instance) { + Ok(_) => false, + Err(index) => { + write_lock.insert(index, instance); + true + } + } } /// Removes an instance from the set. Returns whether the instance was present in the set. @@ -165,11 +170,17 @@ impl Global { pub unsafe fn remove_instance(&self, instance: &vulkan::VkInstance) -> bool { //println!("remove_instance called!"); let mut write_lock = VK_INSTANCE.write().unwrap(); - write_lock.remove(instance) + match write_lock.binary_search(&instance) { + Ok(index) => { + write_lock.remove(index); + true + } + Err(_) => false, + } } // This is safe since vulkan will just discard garbage values - /// Adds a device to the internal HashSet. + /// Adds a device to the internal Vec. /// /// Returns whether the device was newly inserted. That is: /// @@ -179,8 +190,14 @@ impl Global { /// *note: This function returns `false` if the device is valid and defined through dylink.* pub fn insert_device(&self, device: vulkan::VkDevice) -> bool { //println!("insert_device called!"); - let mut write_lock = VK_DEVICE.write().unwrap(); - write_lock.insert(device) + let mut write_lock = VK_DEVICE.write().unwrap(); + match write_lock.binary_search(&device) { + Ok(_) => false, + Err(index) => { + write_lock.insert(index, device); + true + } + } } /// Removes a device from the set. Returns whether the value was present in the set. @@ -189,7 +206,13 @@ impl Global { pub unsafe fn remove_device(&self, device: &vulkan::VkDevice) -> bool { //println!("remove_device called!"); let mut write_lock = VK_DEVICE.write().unwrap(); - write_lock.remove(device) + match write_lock.binary_search(&device) { + Ok(index) => { + write_lock.remove(index); + true + } + Err(_) => false, + } } } diff --git a/src/vulkan.rs b/src/vulkan.rs index 58a7dd5..ea8ef40 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -13,14 +13,14 @@ extern crate self as dylink; #[doc(hidden)] #[repr(transparent)] -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct VkInstance(pub(crate) *mut ffi::c_void); unsafe impl Sync for VkInstance {} unsafe impl Send for VkInstance {} #[doc(hidden)] #[repr(transparent)] -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct VkDevice(pub(crate) *mut ffi::c_void); unsafe impl Sync for VkDevice {} unsafe impl Send for VkDevice {} From c2f9c681850dda101e489bcda307f928f8854a29 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 20 May 2023 00:36:13 -0700 Subject: [PATCH 04/21] simplified `vulkan_loader` --- src/lazyfn.rs | 6 ++++- src/lazyfn/loader.rs | 52 ++++++++++++++++++++------------------------ src/lib.rs | 15 +++++-------- src/vulkan.rs | 13 +++++++---- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index d503045..074006e 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -96,7 +96,11 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { pub(crate) fn try_link_with(&self) -> crate::Result<&F> { self.once.call_once(|| { let maybe = match self.link_ty { - LinkType::Vulkan => unsafe { loader::vulkan_loader(self.fn_name) }, + LinkType::Vulkan => unsafe { + loader::vulkan_loader(self.fn_name).ok_or(error::DylinkError::FnNotFound( + self.fn_name.to_str().unwrap().to_owned(), + )) + }, LinkType::General(lib_list) => { let mut errors = vec![]; lib_list diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index 7fb6867..c47e753 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -4,14 +4,16 @@ use std::{ffi, mem, sync::RwLock}; use crate::{error::*, vulkan, FnPtr, Result}; -pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Result { +pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Option { let mut maybe_fn = crate::VK_DEVICE .read() .expect("failed to get read lock") .iter() - .find_map(|device| vulkan::vkGetDeviceProcAddr(*device, fn_name.as_ptr() as *const _)); + .find_map(|device| { + vulkan::vkGetDeviceProcAddr(*device, fn_name.as_ptr() as *const ffi::c_char) + }); maybe_fn = match maybe_fn { - Some(addr) => return Ok(addr), + Some(addr) => return Some(addr), None => crate::VK_INSTANCE .read() .expect("failed to get read lock") @@ -21,14 +23,11 @@ pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Result { }), }; match maybe_fn { - Some(addr) => Ok(addr), + Some(addr) => Some(addr), None => vulkan::vkGetInstanceProcAddr( - vulkan::VkInstance(std::ptr::null_mut()), + vulkan::VkInstance::null(), fn_name.as_ptr() as *const ffi::c_char, - ) - .ok_or(DylinkError::FnNotFound( - fn_name.to_str().unwrap().to_owned(), - )), + ), } } @@ -37,35 +36,32 @@ pub(crate) fn general_loader( lib_name: &ffi::CStr, fn_name: &ffi::CStr, ) -> Result { - static DLL_DATA: RwLock> = - RwLock::new(Vec::new()); + static DLL_DATA: RwLock> = RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. let maybe_fn; let read_lock = DLL_DATA.read().unwrap(); match read_lock.binary_search_by_key(&lib_name, |(k, _)| k) { - Ok(index) => { - maybe_fn = L::load_sym(&read_lock[index].1, fn_name) - } - Err(index) => { - mem::drop(read_lock); + Ok(index) => maybe_fn = L::load_sym(&read_lock[index].1, fn_name), + Err(index) => { + mem::drop(read_lock); - let lib_handle = L::load_lib(lib_name); + let lib_handle = L::load_lib(lib_name); - if lib_handle.is_invalid() { - return Err(DylinkError::LibNotLoaded( - lib_name.to_string_lossy().into_owned(), - )); - } else { - maybe_fn = L::load_sym(&lib_handle, fn_name); - DLL_DATA - .write() - .unwrap() - .insert(index, (lib_name.to_owned(), lib_handle)); + if lib_handle.is_invalid() { + return Err(DylinkError::LibNotLoaded( + lib_name.to_string_lossy().into_owned(), + )); + } else { + maybe_fn = L::load_sym(&lib_handle, fn_name); + DLL_DATA + .write() + .unwrap() + .insert(index, (lib_name.to_owned(), lib_handle)); + } } } -} match maybe_fn { Some(addr) => Ok(addr), None => Err(DylinkError::FnNotFound( diff --git a/src/lib.rs b/src/lib.rs index f570e87..9609e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,11 +120,9 @@ compile_error!("Dylink Error: Wasm is unsupported."); // These globals are read every time a vulkan function is called for the first time, // which occurs through `LazyFn::link`. -static VK_INSTANCE: sync::RwLock> = - sync::RwLock::new(Vec::new()); +static VK_INSTANCE: sync::RwLock> = sync::RwLock::new(Vec::new()); -static VK_DEVICE: sync::RwLock> = - sync::RwLock::new(Vec::new()); +static VK_DEVICE: sync::RwLock> = sync::RwLock::new(Vec::new()); // Used as a placeholder function pointer. This should **NEVER** be called directly, // and promptly cast into the correct function pointer type. @@ -132,8 +130,7 @@ pub(crate) type FnPtr = unsafe extern "system" fn() -> isize; // The result of a dylink function pub(crate) type Result = std::result::Result; -// TODO: Make the `Global` struct below public when extern types are stable. -// The name `Global` is still TBD. +// TODO: Make the `Global` struct below public when name is picked out /// The global context for specializations. /// @@ -170,7 +167,7 @@ impl Global { pub unsafe fn remove_instance(&self, instance: &vulkan::VkInstance) -> bool { //println!("remove_instance called!"); let mut write_lock = VK_INSTANCE.write().unwrap(); - match write_lock.binary_search(&instance) { + match write_lock.binary_search(instance) { Ok(index) => { write_lock.remove(index); true @@ -190,7 +187,7 @@ impl Global { /// *note: This function returns `false` if the device is valid and defined through dylink.* pub fn insert_device(&self, device: vulkan::VkDevice) -> bool { //println!("insert_device called!"); - let mut write_lock = VK_DEVICE.write().unwrap(); + let mut write_lock = VK_DEVICE.write().unwrap(); match write_lock.binary_search(&device) { Ok(_) => false, Err(index) => { @@ -206,7 +203,7 @@ impl Global { pub unsafe fn remove_device(&self, device: &vulkan::VkDevice) -> bool { //println!("remove_device called!"); let mut write_lock = VK_DEVICE.write().unwrap(); - match write_lock.binary_search(&device) { + match write_lock.binary_search(device) { Ok(index) => { write_lock.remove(index); true diff --git a/src/vulkan.rs b/src/vulkan.rs index ea8ef40..b24f8a4 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -13,15 +13,20 @@ extern crate self as dylink; #[doc(hidden)] #[repr(transparent)] -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct VkInstance(pub(crate) *mut ffi::c_void); +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VkInstance(*mut ffi::c_void); unsafe impl Sync for VkInstance {} unsafe impl Send for VkInstance {} +impl VkInstance { + pub const fn null() -> Self { + Self(std::ptr::null_mut()) + } +} #[doc(hidden)] #[repr(transparent)] -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct VkDevice(pub(crate) *mut ffi::c_void); +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VkDevice(*mut ffi::c_void); unsafe impl Sync for VkDevice {} unsafe impl Send for VkDevice {} From 91bd0edea822bec9bd6d5dd4dc9ee70934a2c8ee Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 21 May 2023 00:20:04 -0700 Subject: [PATCH 05/21] made `try_link_with` public --- src/lazyfn.rs | 6 +++--- src/lazyfn/loader.rs | 11 +++++++---- src/lazyfn/unix.rs | 16 ++++++++++++++-- src/lazyfn/win32.rs | 13 +++++++++++-- src/lib.rs | 32 +++++++++++++++++++++++++------- 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index 074006e..e286e9d 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -16,7 +16,7 @@ use std::{ }, }; -use crate::error; +use crate::{error, DylinkResult}; struct DefaultLinker; @@ -87,13 +87,13 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { /// } /// } /// ``` - pub fn try_link(&self) -> crate::Result<&F> { + pub fn try_link(&self) -> DylinkResult<&F> { self.try_link_with::() } /// Provides a generic argument to supply a user defined linker loader to load the library. /// If successful, stores address in current instance and returns a reference of the stored value. - pub(crate) fn try_link_with(&self) -> crate::Result<&F> { + pub fn try_link_with(&self) -> DylinkResult<&F> { self.once.call_once(|| { let maybe = match self.link_ty { LinkType::Vulkan => unsafe { diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index c47e753..f380f29 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -2,7 +2,7 @@ use std::{ffi, mem, sync::RwLock}; -use crate::{error::*, vulkan, FnPtr, Result}; +use crate::{error::*, vulkan, DylinkResult, FnPtr}; pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Option { let mut maybe_fn = crate::VK_DEVICE @@ -35,7 +35,7 @@ pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Option { pub(crate) fn general_loader( lib_name: &ffi::CStr, fn_name: &ffi::CStr, -) -> Result { +) -> DylinkResult { static DLL_DATA: RwLock> = RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. @@ -43,7 +43,10 @@ pub(crate) fn general_loader( let read_lock = DLL_DATA.read().unwrap(); match read_lock.binary_search_by_key(&lib_name, |(k, _)| k) { - Ok(index) => maybe_fn = L::load_sym(&read_lock[index].1, fn_name), + Ok(index) => { + let handle = crate::LibHandle::(read_lock[index].1 .0.cast()); + maybe_fn = L::load_sym(&handle, fn_name) + } Err(index) => { mem::drop(read_lock); @@ -58,7 +61,7 @@ pub(crate) fn general_loader( DLL_DATA .write() .unwrap() - .insert(index, (lib_name.to_owned(), lib_handle)); + .insert(index, (lib_name.to_owned(), lib_handle.as_opaque())); } } } diff --git a/src/lazyfn/unix.rs b/src/lazyfn/unix.rs index e2ac6fc..0b94ac4 100644 --- a/src/lazyfn/unix.rs +++ b/src/lazyfn/unix.rs @@ -13,10 +13,22 @@ extern "C" { } impl crate::RTLinker for DefaultLinker { + type Data = c_void; fn load_lib(lib_name: &CStr) -> LibHandle { - unsafe { LibHandle(dlopen(lib_name.as_ptr(), RTLD_NOW | RTLD_LOCAL)) } + unsafe { + let result = dlopen(lib_name.as_ptr(), RTLD_NOW | RTLD_LOCAL); + LibHandle::from(result.as_ref()) + } } fn load_sym(lib_handle: &LibHandle, fn_name: &CStr) -> Option { - unsafe { dlsym(lib_handle.0.cast_mut(), fn_name.as_ptr()) } + unsafe { + dlsym( + lib_handle + .as_ref() + .map(|r| r as *const _ as *mut c_void) + .unwrap_or(std::ptr::null_mut()), + fn_name.as_ptr(), + ) + } } } diff --git a/src/lazyfn/win32.rs b/src/lazyfn/win32.rs index 045839c..29d111d 100644 --- a/src/lazyfn/win32.rs +++ b/src/lazyfn/win32.rs @@ -18,6 +18,7 @@ extern "system" { } impl crate::RTLinker for DefaultLinker { + type Data = ffi::c_void; fn load_lib(lib_name: &ffi::CStr) -> LibHandle { let wide_str: Vec = lib_name .to_string_lossy() @@ -32,9 +33,17 @@ impl crate::RTLinker for DefaultLinker { LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, ) }; - LibHandle(result) + LibHandle::from(unsafe { result.as_ref() }) } fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option { - unsafe { GetProcAddress(lib_handle.0.cast_mut(), fn_name.as_ptr().cast()) } + unsafe { + GetProcAddress( + lib_handle + .as_ref() + .map(|r| r as *const _ as *mut ffi::c_void) + .unwrap_or(std::ptr::null_mut()), + fn_name.as_ptr().cast(), + ) + } } } diff --git a/src/lib.rs b/src/lib.rs index 9609e78..46c0346 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,8 +127,9 @@ static VK_DEVICE: sync::RwLock> = sync::RwLock::new(Vec::n // Used as a placeholder function pointer. This should **NEVER** be called directly, // and promptly cast into the correct function pointer type. pub(crate) type FnPtr = unsafe extern "system" fn() -> isize; + // The result of a dylink function -pub(crate) type Result = std::result::Result; +pub(crate) type DylinkResult = Result; // TODO: Make the `Global` struct below public when name is picked out @@ -214,19 +215,36 @@ impl Global { } /// Opaque pointer sized library handle +#[derive(Clone, Copy)] #[repr(transparent)] -pub(crate) struct LibHandle(*const ffi::c_void); +pub struct LibHandle(*const T); unsafe impl Send for LibHandle {} unsafe impl Sync for LibHandle {} -impl LibHandle { +impl LibHandle { #[inline] - fn is_invalid(&self) -> bool { + pub fn is_invalid(&self) -> bool { self.0.is_null() } + pub(crate) fn as_opaque(&self) -> LibHandle { + LibHandle(self.0.cast()) + } + pub fn as_ref(&self) -> Option<&T> { + unsafe { self.0.as_ref() } + } +} + +impl From> for LibHandle { + fn from(value: Option<&T>) -> Self { + value + .map(|r| Self((r as *const T).cast())) + .unwrap_or(Self(std::ptr::null())) + } } -pub(crate) trait RTLinker { - fn load_lib(lib_name: &ffi::CStr) -> LibHandle; - fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option; +/// Used to specify a custom linker loader for `LazyFn` +pub trait RTLinker { + type Data; + fn load_lib(lib_name: &ffi::CStr) -> LibHandle; + fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option; } From 412199cd89ac22dcf11a5d76897d14ae8eedc2fe Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 21 May 2023 00:35:18 -0700 Subject: [PATCH 06/21] removed LibHandle generic default --- src/lazyfn/loader.rs | 2 +- src/lazyfn/unix.rs | 4 ++-- src/lazyfn/win32.rs | 4 ++-- src/lib.rs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index f380f29..51d355e 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -36,7 +36,7 @@ pub(crate) fn general_loader( lib_name: &ffi::CStr, fn_name: &ffi::CStr, ) -> DylinkResult { - static DLL_DATA: RwLock> = RwLock::new(Vec::new()); + static DLL_DATA: RwLock)>> = RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. let maybe_fn; diff --git a/src/lazyfn/unix.rs b/src/lazyfn/unix.rs index 0b94ac4..3915454 100644 --- a/src/lazyfn/unix.rs +++ b/src/lazyfn/unix.rs @@ -14,13 +14,13 @@ extern "C" { impl crate::RTLinker for DefaultLinker { type Data = c_void; - fn load_lib(lib_name: &CStr) -> LibHandle { + fn load_lib(lib_name: &CStr) -> LibHandle { unsafe { let result = dlopen(lib_name.as_ptr(), RTLD_NOW | RTLD_LOCAL); LibHandle::from(result.as_ref()) } } - fn load_sym(lib_handle: &LibHandle, fn_name: &CStr) -> Option { + fn load_sym(lib_handle: &LibHandle, fn_name: &CStr) -> Option { unsafe { dlsym( lib_handle diff --git a/src/lazyfn/win32.rs b/src/lazyfn/win32.rs index 29d111d..f32a2b9 100644 --- a/src/lazyfn/win32.rs +++ b/src/lazyfn/win32.rs @@ -19,7 +19,7 @@ extern "system" { impl crate::RTLinker for DefaultLinker { type Data = ffi::c_void; - fn load_lib(lib_name: &ffi::CStr) -> LibHandle { + fn load_lib(lib_name: &ffi::CStr) -> LibHandle { let wide_str: Vec = lib_name .to_string_lossy() .encode_utf16() @@ -35,7 +35,7 @@ impl crate::RTLinker for DefaultLinker { }; LibHandle::from(unsafe { result.as_ref() }) } - fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option { + fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option { unsafe { GetProcAddress( lib_handle diff --git a/src/lib.rs b/src/lib.rs index 46c0346..7c57ae8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,9 +217,9 @@ impl Global { /// Opaque pointer sized library handle #[derive(Clone, Copy)] #[repr(transparent)] -pub struct LibHandle(*const T); -unsafe impl Send for LibHandle {} -unsafe impl Sync for LibHandle {} +pub struct LibHandle(*const T); +unsafe impl Send for LibHandle {} +unsafe impl Sync for LibHandle {} impl LibHandle { #[inline] From 563a925f4fc69fa7e240081cd0fd4768e2b4d313 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 21 May 2023 01:00:27 -0700 Subject: [PATCH 07/21] added thread-safety constraints to `LibHandle` --- src/lazyfn.rs | 5 ++++- src/lazyfn/loader.rs | 5 ++++- src/lib.rs | 15 ++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index e286e9d..adef1f7 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -93,7 +93,10 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { /// Provides a generic argument to supply a user defined linker loader to load the library. /// If successful, stores address in current instance and returns a reference of the stored value. - pub fn try_link_with(&self) -> DylinkResult<&F> { + pub fn try_link_with(&self) -> DylinkResult<&F> + where + L::Data: Send + Sync, + { self.once.call_once(|| { let maybe = match self.link_ty { LinkType::Vulkan => unsafe { diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index 51d355e..55c7eb2 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -35,7 +35,10 @@ pub(crate) unsafe fn vulkan_loader(fn_name: &ffi::CStr) -> Option { pub(crate) fn general_loader( lib_name: &ffi::CStr, fn_name: &ffi::CStr, -) -> DylinkResult { +) -> DylinkResult +where + L::Data: Send + Sync, +{ static DLL_DATA: RwLock)>> = RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. diff --git a/src/lib.rs b/src/lib.rs index 7c57ae8..2c02691 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,12 +214,13 @@ impl Global { } } +// LibHandle is thread-safe because it's inherently immutable, therefore don't add mutable accessors. + /// Opaque pointer sized library handle -#[derive(Clone, Copy)] #[repr(transparent)] pub struct LibHandle(*const T); -unsafe impl Send for LibHandle {} -unsafe impl Sync for LibHandle {} +unsafe impl Send for LibHandle where T: Send {} +unsafe impl Sync for LibHandle where T: Sync {} impl LibHandle { #[inline] @@ -245,6 +246,10 @@ impl From> for LibHandle { /// Used to specify a custom linker loader for `LazyFn` pub trait RTLinker { type Data; - fn load_lib(lib_name: &ffi::CStr) -> LibHandle; - fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option; + fn load_lib(lib_name: &ffi::CStr) -> LibHandle + where + LibHandle: Send + Sync; + fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option + where + LibHandle: Send + Sync; } From af6eb9a64e6741cf7d192231c86bfede9a0f1dc1 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 21 May 2023 01:10:29 -0700 Subject: [PATCH 08/21] enhanced documentation --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2c02691..eae87bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! The internal copies of the instance and device handles are only stored until destroyed through a dylink generated vulkan function. //! //! *Note: Due to how dylink handles loading, `vkCreateInstance`, `vkDestroyInstance`, `vkCreateDevice`, and `vkDestroyDevice` are -//! incompatible with the `strip=true` macro argument.* +//! incompatible with the `strip=true` macro argument with the `#[dylink(vulkan)]` specialization.* //! ```rust //! use dylink::dylink; //! @@ -92,6 +92,8 @@ //! Shared library unloading is extremely cursed, always unsafe, and we don't even try to support it. //! Unloading a library means not only are all loaded dylink functions invalidated, but functions loaded from **ALL** //! crates in the project are also invalidated, which will immediately lead to segfaults... a lot of them. +//! +//! *An unloader may be considered in future revisions, but the current abstraction is unsuitable for RAII unloading.* use std::sync; @@ -243,7 +245,7 @@ impl From> for LibHandle { } } -/// Used to specify a custom linker loader for `LazyFn` +/// Used to specify a custom run-time linker loader for [LazyFn] pub trait RTLinker { type Data; fn load_lib(lib_name: &ffi::CStr) -> LibHandle From b747b04446892deba04ad562e8c3080f13a46163 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 21 May 2023 01:27:18 -0700 Subject: [PATCH 09/21] annotated LibHandle * removed `#[repr(transparent)]` from LibHandle * annotated lifetimes everywhere to confirm everything lives long enough --- src/lazyfn/loader.rs | 4 ++-- src/lazyfn/unix.rs | 4 ++-- src/lazyfn/win32.rs | 4 ++-- src/lib.rs | 31 ++++++++++++++++--------------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index 55c7eb2..a4f9bbd 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -1,6 +1,6 @@ // Copyright (c) 2023 Jonathan "Razordor" Alan Thomason -use std::{ffi, mem, sync::RwLock}; +use std::{ffi, mem, sync::RwLock, marker::PhantomData}; use crate::{error::*, vulkan, DylinkResult, FnPtr}; @@ -47,7 +47,7 @@ where let read_lock = DLL_DATA.read().unwrap(); match read_lock.binary_search_by_key(&lib_name, |(k, _)| k) { Ok(index) => { - let handle = crate::LibHandle::(read_lock[index].1 .0.cast()); + let handle = crate::LibHandle::(read_lock[index].1 .0.cast(), PhantomData); maybe_fn = L::load_sym(&handle, fn_name) } Err(index) => { diff --git a/src/lazyfn/unix.rs b/src/lazyfn/unix.rs index 3915454..64d307d 100644 --- a/src/lazyfn/unix.rs +++ b/src/lazyfn/unix.rs @@ -14,13 +14,13 @@ extern "C" { impl crate::RTLinker for DefaultLinker { type Data = c_void; - fn load_lib(lib_name: &CStr) -> LibHandle { + fn load_lib(lib_name: &CStr) -> LibHandle<'static, Self::Data> { unsafe { let result = dlopen(lib_name.as_ptr(), RTLD_NOW | RTLD_LOCAL); LibHandle::from(result.as_ref()) } } - fn load_sym(lib_handle: &LibHandle, fn_name: &CStr) -> Option { + fn load_sym(lib_handle: &LibHandle<'static, Self::Data>, fn_name: &CStr) -> Option { unsafe { dlsym( lib_handle diff --git a/src/lazyfn/win32.rs b/src/lazyfn/win32.rs index f32a2b9..3d2e14b 100644 --- a/src/lazyfn/win32.rs +++ b/src/lazyfn/win32.rs @@ -19,7 +19,7 @@ extern "system" { impl crate::RTLinker for DefaultLinker { type Data = ffi::c_void; - fn load_lib(lib_name: &ffi::CStr) -> LibHandle { + fn load_lib(lib_name: &ffi::CStr) -> LibHandle<'static, Self::Data> { let wide_str: Vec = lib_name .to_string_lossy() .encode_utf16() @@ -35,7 +35,7 @@ impl crate::RTLinker for DefaultLinker { }; LibHandle::from(unsafe { result.as_ref() }) } - fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option { + fn load_sym(lib_handle: &LibHandle<'static, Self::Data>, fn_name: &ffi::CStr) -> Option { unsafe { GetProcAddress( lib_handle diff --git a/src/lib.rs b/src/lib.rs index eae87bd..bcfcd9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,7 @@ //! //! *An unloader may be considered in future revisions, but the current abstraction is unsuitable for RAII unloading.* +use std::marker::PhantomData; use std::sync; use std::ffi; @@ -218,40 +219,40 @@ impl Global { // LibHandle is thread-safe because it's inherently immutable, therefore don't add mutable accessors. -/// Opaque pointer sized library handle -#[repr(transparent)] -pub struct LibHandle(*const T); -unsafe impl Send for LibHandle where T: Send {} -unsafe impl Sync for LibHandle where T: Sync {} +/// Library handle for [RTLinker] +pub struct LibHandle<'a, T>(*const T, PhantomData<&'a()>); +unsafe impl Send for LibHandle<'_, T> where T: Send {} +unsafe impl Sync for LibHandle<'_, T> where T: Sync {} -impl LibHandle { +impl<'a, T> LibHandle<'a, T> { #[inline] pub fn is_invalid(&self) -> bool { self.0.is_null() } - pub(crate) fn as_opaque(&self) -> LibHandle { - LibHandle(self.0.cast()) + // This is basically a clone to an opaque handle + pub(crate) fn as_opaque<'b>(&'a self) -> LibHandle<'b, ffi::c_void> { + LibHandle(self.0.cast(), PhantomData) } pub fn as_ref(&self) -> Option<&T> { unsafe { self.0.as_ref() } } } -impl From> for LibHandle { +impl<'a, T> From> for LibHandle<'a, T> { fn from(value: Option<&T>) -> Self { value - .map(|r| Self((r as *const T).cast())) - .unwrap_or(Self(std::ptr::null())) + .map(|r| Self((r as *const T).cast(), PhantomData)) + .unwrap_or(Self(std::ptr::null(), PhantomData)) } } /// Used to specify a custom run-time linker loader for [LazyFn] pub trait RTLinker { type Data; - fn load_lib(lib_name: &ffi::CStr) -> LibHandle + fn load_lib(lib_name: &ffi::CStr) -> LibHandle<'static, Self::Data> where - LibHandle: Send + Sync; - fn load_sym(lib_handle: &LibHandle, fn_name: &ffi::CStr) -> Option + Self::Data: Send + Sync; + fn load_sym(lib_handle: &LibHandle<'static, Self::Data>, fn_name: &ffi::CStr) -> Option where - LibHandle: Send + Sync; + Self::Data: Send + Sync; } From 3c0717cbedddf716a1385b8ab6af2288329883d8 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 22 May 2023 22:08:46 -0700 Subject: [PATCH 10/21] added `From` option to LibHandle removed reference --- src/lazyfn.rs | 2 ++ src/lib.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index adef1f7..d38667a 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -153,6 +153,8 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { } } +// should this be removed in favor of just calling load? + impl std::ops::Deref for LazyFn<'_, F> { type Target = F; /// Dereferences the value atomically. diff --git a/src/lib.rs b/src/lib.rs index bcfcd9a..d31f158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,7 +220,7 @@ impl Global { // LibHandle is thread-safe because it's inherently immutable, therefore don't add mutable accessors. /// Library handle for [RTLinker] -pub struct LibHandle<'a, T>(*const T, PhantomData<&'a()>); +pub struct LibHandle<'a, T: ?Sized>(*const T, PhantomData<&'a()>); unsafe impl Send for LibHandle<'_, T> where T: Send {} unsafe impl Sync for LibHandle<'_, T> where T: Sync {} @@ -239,13 +239,19 @@ impl<'a, T> LibHandle<'a, T> { } impl<'a, T> From> for LibHandle<'a, T> { - fn from(value: Option<&T>) -> Self { + fn from(value: Option<&'a T>) -> Self { value .map(|r| Self((r as *const T).cast(), PhantomData)) .unwrap_or(Self(std::ptr::null(), PhantomData)) } } +impl<'a, T> From<&'a T> for LibHandle<'a, T> { + fn from(value: &'a T) -> Self { + Self((value as *const T).cast(), PhantomData) + } +} + /// Used to specify a custom run-time linker loader for [LazyFn] pub trait RTLinker { type Data; From e29aa646e99a590e4a1045e1596e8ddc7823a3ca Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 May 2023 18:30:18 -0700 Subject: [PATCH 11/21] optimized macro formatted code --- dylink_macro/src/attr_data.rs | 22 +++++++++++++--------- dylink_macro/src/lib.rs | 12 ++++++------ src/lazyfn/loader.rs | 5 +++-- src/lazyfn/unix.rs | 5 ++++- src/lazyfn/win32.rs | 5 ++++- src/lib.rs | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/dylink_macro/src/attr_data.rs b/dylink_macro/src/attr_data.rs index d1939df..f2217be 100644 --- a/dylink_macro/src/attr_data.rs +++ b/dylink_macro/src/attr_data.rs @@ -1,11 +1,11 @@ // Copyright (c) 2023 Jonathan "Razordor" Alan Thomason -use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use std::str::FromStr; use syn::punctuated::Punctuated; use syn::{spanned::Spanned, *}; pub struct AttrData { - pub strip: Option, + pub strip: bool, pub link_ty: LinkType, } @@ -19,7 +19,7 @@ pub enum LinkType { impl TryFrom> for AttrData { type Error = syn::Error; fn try_from(value: Punctuated) -> Result { - let mut strip: Option = None; + let mut strip = false; let mut maybe_link_ty: Option = None; let mut errors = vec![]; const EXPECTED_KW: &str = "Expected `vulkan`, `any`, `strip`, or `name`."; @@ -41,8 +41,11 @@ impl TryFrom> for AttrData { // Branch for syntax: #[dylink(name = "")] Expr::Assign(assign) => { let (assign_left, assign_right) = (assign.left.as_ref(), assign.right.as_ref()); - if matches!(assign_left, Expr::Path(ExprPath { path, .. }) if path.is_ident("name")) - { + + let Expr::Path(ExprPath { path, .. }) = assign_left else { + unreachable!("internal error when parsing Expr::Assign"); + }; + if path.is_ident("name") { match assign_right { Expr::Lit(ExprLit { lit: Lit::Str(lib), .. @@ -60,16 +63,17 @@ impl TryFrom> for AttrData { errors.push(Error::new(right.span(), "Expected string literal.")) } } - } else if matches!(assign_left, Expr::Path(ExprPath { path, .. }) if path.is_ident("strip")) - { + } else if path.is_ident("strip") { + // Branch for syntax: #[dylink(strip = )] + match assign_right { Expr::Lit(ExprLit { lit: Lit::Bool(val), .. }) => { if val.value() { - if strip.is_none() { - strip = Some(val.span()) + if strip == false { + strip = true; } else { errors.push(Error::new( assign.span(), diff --git a/dylink_macro/src/lib.rs b/dylink_macro/src/lib.rs index f269416..b930e90 100644 --- a/dylink_macro/src/lib.rs +++ b/dylink_macro/src/lib.rs @@ -75,7 +75,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) } // this is sure to obfuscate things, but this is needed here because `strip` screws with call context. - let caller_name = if strip.is_some() { + let caller_name = if strip { quote! {function} } else { quote! {DYN_FUNC} @@ -86,7 +86,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) let call_dyn_func = if *link_ty == LinkType::Vulkan { match fn_name.to_string().as_str() { "vkCreateInstance" => { - if strip.is_some() { + if strip { return syn::Error::new( fn_item.span(), "`vkCreateInstance` is incompatible with `strip=true`", @@ -105,7 +105,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) } } "vkDestroyInstance" => { - if strip.is_some() { + if strip { return syn::Error::new( fn_item.span(), "`vkDestroyInstance` is incompatible with `strip=true`", @@ -122,7 +122,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) } } "vkCreateDevice" => { - if strip.is_some() { + if strip { return syn::Error::new( fn_item.span(), "`vkCreateDevice` is incompatible with `strip=true`", @@ -139,7 +139,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) } } "vkDestroyDevice" => { - if strip.is_some() { + if strip { return syn::Error::new( fn_item.span(), "`vkDestroyDevice` is incompatible with `strip=true`", @@ -163,7 +163,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) // According to "The Rustonomicon" foreign functions are assumed unsafe, // so functions are implicitly prepended with `unsafe` - if strip.is_some() { + if strip { quote! { #(#fn_attrs)* #[allow(non_upper_case_globals)] diff --git a/src/lazyfn/loader.rs b/src/lazyfn/loader.rs index a4f9bbd..fa98d34 100644 --- a/src/lazyfn/loader.rs +++ b/src/lazyfn/loader.rs @@ -1,6 +1,6 @@ // Copyright (c) 2023 Jonathan "Razordor" Alan Thomason -use std::{ffi, mem, sync::RwLock, marker::PhantomData}; +use std::{ffi, marker::PhantomData, mem, sync::RwLock}; use crate::{error::*, vulkan, DylinkResult, FnPtr}; @@ -39,7 +39,8 @@ pub(crate) fn general_loader( where L::Data: Send + Sync, { - static DLL_DATA: RwLock)>> = RwLock::new(Vec::new()); + static DLL_DATA: RwLock)>> = + RwLock::new(Vec::new()); // somehow rust is smart enough to infer that maybe_fn is assigned to only once after branching. let maybe_fn; diff --git a/src/lazyfn/unix.rs b/src/lazyfn/unix.rs index 64d307d..ce1ff6f 100644 --- a/src/lazyfn/unix.rs +++ b/src/lazyfn/unix.rs @@ -20,7 +20,10 @@ impl crate::RTLinker for DefaultLinker { LibHandle::from(result.as_ref()) } } - fn load_sym(lib_handle: &LibHandle<'static, Self::Data>, fn_name: &CStr) -> Option { + fn load_sym( + lib_handle: &LibHandle<'static, Self::Data>, + fn_name: &CStr, + ) -> Option { unsafe { dlsym( lib_handle diff --git a/src/lazyfn/win32.rs b/src/lazyfn/win32.rs index 3d2e14b..b2c6511 100644 --- a/src/lazyfn/win32.rs +++ b/src/lazyfn/win32.rs @@ -35,7 +35,10 @@ impl crate::RTLinker for DefaultLinker { }; LibHandle::from(unsafe { result.as_ref() }) } - fn load_sym(lib_handle: &LibHandle<'static, Self::Data>, fn_name: &ffi::CStr) -> Option { + fn load_sym( + lib_handle: &LibHandle<'static, Self::Data>, + fn_name: &ffi::CStr, + ) -> Option { unsafe { GetProcAddress( lib_handle diff --git a/src/lib.rs b/src/lib.rs index d31f158..4b0b660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,7 @@ //! Shared library unloading is extremely cursed, always unsafe, and we don't even try to support it. //! Unloading a library means not only are all loaded dylink functions invalidated, but functions loaded from **ALL** //! crates in the project are also invalidated, which will immediately lead to segfaults... a lot of them. -//! +//! //! *An unloader may be considered in future revisions, but the current abstraction is unsuitable for RAII unloading.* use std::marker::PhantomData; @@ -220,7 +220,7 @@ impl Global { // LibHandle is thread-safe because it's inherently immutable, therefore don't add mutable accessors. /// Library handle for [RTLinker] -pub struct LibHandle<'a, T: ?Sized>(*const T, PhantomData<&'a()>); +pub struct LibHandle<'a, T: ?Sized>(*const T, PhantomData<&'a ()>); unsafe impl Send for LibHandle<'_, T> where T: Send {} unsafe impl Sync for LibHandle<'_, T> where T: Sync {} From 93ed2105e46144ced747c6634f3283da49173f2b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 May 2023 19:42:25 -0700 Subject: [PATCH 12/21] added option to macro * added `linker` option to macro --- dylink_macro/src/attr_data.rs | 43 ++++++++++++++++++++++++----------- dylink_macro/src/lib.rs | 15 ++++++++++-- src/lazyfn/win32.rs | 24 +++++++++++++++++++ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/dylink_macro/src/attr_data.rs b/dylink_macro/src/attr_data.rs index f2217be..7b85813 100644 --- a/dylink_macro/src/attr_data.rs +++ b/dylink_macro/src/attr_data.rs @@ -7,6 +7,7 @@ use syn::{spanned::Spanned, *}; pub struct AttrData { pub strip: bool, pub link_ty: LinkType, + pub linker: Option, } #[derive(PartialEq)] @@ -19,8 +20,9 @@ pub enum LinkType { impl TryFrom> for AttrData { type Error = syn::Error; fn try_from(value: Punctuated) -> Result { - let mut strip = false; + let mut maybe_strip: Option = None; let mut maybe_link_ty: Option = None; + let mut linker: Option = None; let mut errors = vec![]; const EXPECTED_KW: &str = "Expected `vulkan`, `any`, `strip`, or `name`."; @@ -38,7 +40,6 @@ impl TryFrom> for AttrData { errors.push(Error::new(path.span(), EXPECTED_KW)); } } - // Branch for syntax: #[dylink(name = "")] Expr::Assign(assign) => { let (assign_left, assign_right) = (assign.left.as_ref(), assign.right.as_ref()); @@ -46,6 +47,7 @@ impl TryFrom> for AttrData { unreachable!("internal error when parsing Expr::Assign"); }; if path.is_ident("name") { + // Branch for syntax: #[dylink(name = )] match assign_right { Expr::Lit(ExprLit { lit: Lit::Str(lib), .. @@ -65,27 +67,41 @@ impl TryFrom> for AttrData { } } else if path.is_ident("strip") { // Branch for syntax: #[dylink(strip = )] - match assign_right { Expr::Lit(ExprLit { lit: Lit::Bool(val), .. }) => { - if val.value() { - if strip == false { - strip = true; - } else { - errors.push(Error::new( - assign.span(), - "strip is already defined", - )); - } + if maybe_strip.is_none() { + maybe_strip = Some(val.value()); + } else { + errors.push(Error::new( + assign.span(), + "strip is already defined", + )); } } right => { errors.push(Error::new(right.span(), "Expected boolean literal.")) } } + } else if path.is_ident("linker") { + // Branch for syntax: #[dylink(linker = )] + match assign_right { + Expr::Path(ExprPath { path, .. }) => { + if linker.is_none() { + linker = Some(path.get_ident().unwrap().clone()); + } else { + errors.push(Error::new( + assign.span(), + "linker is already defined", + )); + } + } + right => { + errors.push(Error::new(right.span(), "Expected identifier.")) + } + } } else { errors.push(Error::new(assign_left.span(), EXPECTED_KW)); } @@ -156,8 +172,9 @@ impl TryFrom> for AttrData { } } else { Ok(Self { - strip, + strip: maybe_strip.unwrap_or_default(), link_ty: maybe_link_ty.unwrap(), + linker, }) } } diff --git a/dylink_macro/src/lib.rs b/dylink_macro/src/lib.rs index b930e90..e7e6770 100644 --- a/dylink_macro/src/lib.rs +++ b/dylink_macro/src/lib.rs @@ -45,6 +45,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) let output = fn_item.sig.output.to_token_stream(); let strip = attr_data.strip; let link_ty = &attr_data.link_ty; + let linker = &attr_data.linker; let fn_attrs: Vec = fn_item .attrs @@ -161,9 +162,19 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) quote!(#caller_name(#(#param_list),*)) }; + let try_link_call = match linker { + None => quote!{ + try_link + }, + Some(linker_name) => quote!{ + try_link_with::<#linker_name> + }, + }; + // According to "The Rustonomicon" foreign functions are assumed unsafe, // so functions are implicitly prepended with `unsafe` if strip { + quote! { #(#fn_attrs)* #[allow(non_upper_case_globals)] @@ -174,7 +185,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) type InstFnPtr = unsafe #abi fn (#params_default) #output; unsafe #abi fn initial_fn (#(#param_ty_list),*) #output { use std::ffi::CStr; - match #fn_name.try_link() { + match #fn_name.#try_link_call() { Ok(function) => {#call_dyn_func}, Err(err) => panic!("{}", err), } @@ -196,7 +207,7 @@ fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) type InstFnPtr = #abi fn (#params_default) #output; #abi fn initial_fn (#(#param_ty_list),*) #output { use std::ffi::CStr; - match DYN_FUNC.try_link() { + match DYN_FUNC.#try_link_call() { Ok(function) => {function(#(#param_list),*)}, Err(err) => panic!("{}", err), } diff --git a/src/lazyfn/win32.rs b/src/lazyfn/win32.rs index b2c6511..f6b9308 100644 --- a/src/lazyfn/win32.rs +++ b/src/lazyfn/win32.rs @@ -50,3 +50,27 @@ impl crate::RTLinker for DefaultLinker { } } } + +#[test] +fn test_win32_macro_linker() { + extern crate self as dylink; + #[dylink::dylink(name = "Kernel32.dll", strip = true, linker=DefaultLinker)] + extern "stdcall" { + fn SetLastError(_: u32); + } + + // macro output: function + #[dylink::dylink(name = "Kernel32.dll", strip = false, linker=DefaultLinker)] + extern "C" { + fn GetLastError() -> u32; + } + + unsafe { + // static variable has crappy documentation, but can be use for library induction. + match SetLastError.try_link() { + Ok(f) => f(53), + Err(e) => panic!("{}", e), + } + assert_eq!(GetLastError(), 53); + } +} \ No newline at end of file From 44278f238ba6ce76fd648aa7399c138a661e95d3 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 May 2023 20:20:18 -0700 Subject: [PATCH 13/21] applied macro to independent functions --- dylink_macro/src/lib.rs | 33 +++++++++++++++++++-------------- tests/general_tests.rs | 4 +--- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/dylink_macro/src/lib.rs b/dylink_macro/src/lib.rs index e7e6770..70d20d1 100644 --- a/dylink_macro/src/lib.rs +++ b/dylink_macro/src/lib.rs @@ -13,8 +13,6 @@ use syn::ForeignItem; #[proc_macro_attribute] pub fn dylink(args: TokenStream1, input: TokenStream1) -> TokenStream1 { - let foreign_mod = syn::parse2::(input.into()).expect("failed to parse"); - let punct = Parser::parse2( Punctuated::::parse_separated_nonempty, args.into(), @@ -23,24 +21,31 @@ pub fn dylink(args: TokenStream1, input: TokenStream1) -> TokenStream1 { match AttrData::try_from(punct) { Ok(attr_data) => { - let abi = &foreign_mod.abi; - foreign_mod - .items - .iter() - .map(|item| match item { - ForeignItem::Fn(fn_item) => parse_fn(abi, fn_item, &attr_data), - other => quote!(#abi {#other}), - }) - .collect::() - .into() + if let Ok(foreign_mod) = syn::parse2::(input.clone().into()) { + let abi = &foreign_mod.abi; + foreign_mod + .items + .iter() + .map(|item| match item { + ForeignItem::Fn(fn_item) => parse_fn(fn_item, &attr_data), + other => quote!(#abi {#other}), + }) + .collect::() + .into() + } else if let Ok(foreign_fn) = syn::parse2::(input.into()) { + parse_fn(&foreign_fn, &attr_data) + .into() + } else { + panic!("failed to parse"); + } } Err(e) => syn::Error::into_compile_error(e).into(), } } -fn parse_fn(abi: &syn::Abi, fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { +fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { let fn_name = fn_item.sig.ident.to_token_stream(); - let abi = abi.into_token_stream(); + let abi = fn_item.sig.abi.to_token_stream(); let vis = fn_item.vis.to_token_stream(); let output = fn_item.sig.output.to_token_stream(); let strip = attr_data.strip; diff --git a/tests/general_tests.rs b/tests/general_tests.rs index 8762037..a6d75e2 100644 --- a/tests/general_tests.rs +++ b/tests/general_tests.rs @@ -11,9 +11,7 @@ fn test_win32_kernel32() { // macro output: function #[dylink(name = "Kernel32.dll", strip = false)] - extern "C" { - fn GetLastError() -> u32; - } + fn GetLastError() -> u32; unsafe { // static variable has crappy documentation, but can be use for library induction. From 4ec5504c2c78b2f7e6c56129c85aae6e1ae37eb3 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 May 2023 23:51:34 -0700 Subject: [PATCH 14/21] abstract foreign functions now survive `impl` --- dylink_macro/src/lib.rs | 33 +++++++++++++++++++++++---------- tests/general_tests.rs | 24 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/dylink_macro/src/lib.rs b/dylink_macro/src/lib.rs index 70d20d1..19317b4 100644 --- a/dylink_macro/src/lib.rs +++ b/dylink_macro/src/lib.rs @@ -27,13 +27,13 @@ pub fn dylink(args: TokenStream1, input: TokenStream1) -> TokenStream1 { .items .iter() .map(|item| match item { - ForeignItem::Fn(fn_item) => parse_fn(fn_item, &attr_data), + ForeignItem::Fn(fn_item) => parse_fn::(fn_item, &attr_data), other => quote!(#abi {#other}), }) .collect::() .into() } else if let Ok(foreign_fn) = syn::parse2::(input.into()) { - parse_fn(&foreign_fn, &attr_data) + parse_fn::(&foreign_fn, &attr_data) .into() } else { panic!("failed to parse"); @@ -43,7 +43,7 @@ pub fn dylink(args: TokenStream1, input: TokenStream1) -> TokenStream1 { } } -fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { +fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 { let fn_name = fn_item.sig.ident.to_token_stream(); let abi = fn_item.sig.abi.to_token_stream(); let vis = fn_item.vis.to_token_stream(); @@ -60,6 +60,8 @@ fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 let mut param_list = Vec::new(); let mut param_ty_list = Vec::new(); + let mut internal_param_ty_list = Vec::new(); + let mut internal_param_list = Vec::new(); let params_default = fn_item.sig.inputs.to_token_stream(); for (i, arg) in fn_item.sig.inputs.iter().enumerate() { match arg { @@ -71,11 +73,23 @@ fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 _ => unreachable!(), }; param_list.push(param_name.clone()); + internal_param_list.push(param_name.clone()); param_ty_list.push(quote!(#param_name : #ty)); + internal_param_ty_list.push(quote!(#param_name : #ty)); } syn::FnArg::Receiver(rec) => { - return syn::Error::new(rec.span(), "`self` arguments are unsupported") - .into_compile_error(); + if IS_MOD_ITEM || strip { + // TODO: fix error message + return syn::Error::new(rec.span(), "`self` arguments are unsupported in this context") + .into_compile_error(); + } else { + let ty = rec.ty.to_token_stream(); + let param_name = format!("p{i}").parse::().unwrap(); + param_list.push(quote!{self}); + internal_param_list.push(param_name.clone()); + param_ty_list.push(quote!(self : #ty)); + internal_param_ty_list.push(quote!(#param_name : #ty)); + } } } } @@ -178,8 +192,7 @@ fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 // According to "The Rustonomicon" foreign functions are assumed unsafe, // so functions are implicitly prepended with `unsafe` - if strip { - + if strip { quote! { #(#fn_attrs)* #[allow(non_upper_case_globals)] @@ -209,11 +222,11 @@ fn parse_fn(fn_item: &syn::ForeignItemFn, attr_data: &AttrData) -> TokenStream2 #[inline] #vis unsafe #abi fn #fn_name (#(#param_ty_list),*) #output { // InstFnPtr: instance function pointer type - type InstFnPtr = #abi fn (#params_default) #output; - #abi fn initial_fn (#(#param_ty_list),*) #output { + type InstFnPtr = #abi fn (#(#internal_param_ty_list),*) #output; + #abi fn initial_fn (#(#internal_param_ty_list),*) #output { use std::ffi::CStr; match DYN_FUNC.#try_link_call() { - Ok(function) => {function(#(#param_list),*)}, + Ok(function) => {function(#(#internal_param_list),*)}, Err(err) => panic!("{}", err), } } diff --git a/tests/general_tests.rs b/tests/general_tests.rs index a6d75e2..b6a4096 100644 --- a/tests/general_tests.rs +++ b/tests/general_tests.rs @@ -11,7 +11,7 @@ fn test_win32_kernel32() { // macro output: function #[dylink(name = "Kernel32.dll", strip = false)] - fn GetLastError() -> u32; + extern "system" fn GetLastError() -> u32; unsafe { // static variable has crappy documentation, but can be use for library induction. @@ -23,6 +23,28 @@ fn test_win32_kernel32() { } } +#[cfg(windows)] +#[test] +fn test_win32_impl() { + + #[repr(transparent)] + struct Foo(u32); + + impl Foo { + #[dylink(name = "Kernel32.dll")] + extern "stdcall" fn SetLastError(self: Foo); + + #[dylink(name = "Kernel32.dll")] + extern "system" fn GetLastError() -> u32; + } + + let foo = Foo(23); + unsafe { + foo.SetLastError(); + assert!(Foo::GetLastError() == 23) + } +} + // tbh I don't know why this test passes. #[cfg(windows)] #[test] From e1732e6ab8ad99ca5efe543a9ee78118be4163c2 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 00:46:13 -0700 Subject: [PATCH 15/21] added `test_unix_libc` * fixed errors being compiled into a single variant --- src/lazyfn.rs | 12 +++++++++--- tests/general_tests.rs | 30 ++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index d38667a..12e6497 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -114,9 +114,15 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { .ok() }) .ok_or_else(|| { - error::DylinkError::ListNotLoaded( - errors.iter().map(|e| e.to_string() + "\n").collect(), - ) + if errors.len() > 1 { + error::DylinkError::ListNotLoaded( + errors.iter().map(|e| e.to_string() + "\n").collect(), + ) + } else if errors.len() == 1 { + errors[0].clone() + } else { + unreachable!() + } }) } }; diff --git a/tests/general_tests.rs b/tests/general_tests.rs index b6a4096..a2d2a32 100644 --- a/tests/general_tests.rs +++ b/tests/general_tests.rs @@ -29,19 +29,20 @@ fn test_win32_impl() { #[repr(transparent)] struct Foo(u32); - + + // TODO: Self and self (by itself) are impossible to implement, so consider giving hard errors. impl Foo { #[dylink(name = "Kernel32.dll")] extern "stdcall" fn SetLastError(self: Foo); - + #[dylink(name = "Kernel32.dll")] - extern "system" fn GetLastError() -> u32; + extern "system" fn GetLastError() -> Foo; } let foo = Foo(23); unsafe { foo.SetLastError(); - assert!(Foo::GetLastError() == 23) + assert!(Foo::GetLastError().0 == 23) } } @@ -141,3 +142,24 @@ fn test_multiple_lib_panic() { } } } + + +#[cfg(unix)] +#[test] +fn test_unix_libc() { + #[cfg_attr(target_os = "linux", dylink(name = "libc.so.6", strip=true))] + #[cfg_attr(target_os = "macos", dylink(name = "libc.dylib", strip=true))] + extern "C" { + fn foo(); + } + + match foo.try_link() { + Ok(_) => unreachable!(), + Err(DylinkError::FnNotFound(err)) => { + println!("{err}") + } + Err(DylinkError::LibNotLoaded(err)) => panic!("e0\n{err}"), + Err(DylinkError::ListNotLoaded(err)) => panic!("e1\n{err}"), + Err(_) => todo!() + } +} \ No newline at end of file From 2da579a244e85b70b86797d2a83e3adcbb162ba5 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 01:01:24 -0700 Subject: [PATCH 16/21] added to macos workflow --- .github/workflows/rust.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9d86339..50ed5d6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,6 +34,6 @@ jobs: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose --all -# There are currently no tests for macos - #- name: Run tests - # run: cargo test --verbose -- --skip test_vk_instance_layer_properties \ No newline at end of file + - name: Run tests + # Skipping vulkan test because drivers are not guarenteed to be on the system. + run: cargo test --verbose -- --skip test_vk_instance_layer_properties \ No newline at end of file From 4069301a8457dbddcc118ce9fb68d8ae3b22cecf Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 01:11:09 -0700 Subject: [PATCH 17/21] updated readme added status badge macos is support is now a YES --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab72012..c03e656 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dylink -![Crates.io](https://img.shields.io/crates/l/dylink) ![Crates.io](https://img.shields.io/crates/v/dylink) ![Crates.io](https://img.shields.io/crates/d/dylink) ![docs.rs](https://img.shields.io/docsrs/dylink) ![unsafe:yes](https://img.shields.io/badge/unsafe-yes-red) +![Crates.io](https://img.shields.io/crates/l/dylink) ![Crates.io](https://img.shields.io/crates/v/dylink) ![Crates.io](https://img.shields.io/crates/d/dylink) ![docs.rs](https://img.shields.io/docsrs/dylink) [![dylink-rs](https://github.com/Razordor/dylink/actions/workflows/rust.yml/badge.svg)](https://github.com/Razordor/dylink/actions/workflows/rust.yml) ![unsafe:yes](https://img.shields.io/badge/unsafe-yes-red) Dylink provides a run-time dynamic linking framework for lazily evaluating shared libraries such as `.dll` files for windows and `.so` files for unix. When functions are loaded they are evaluated through a thunk for first time calls, which loads the @@ -16,11 +16,11 @@ Related links: ## Supported platforms -Dylink has been implemented for all major platforms aside from WASM, but has only been tested on Windows and Linux. +Dylink has been implemented for all major platforms. -| Windows | Linux | MacOS | WASM | -|:-------:|:-----:|:--------:|------| -| YES | YES | Untested | NO | +| Windows | Linux | MacOS | WASM | +|:-------:|:-----:|:-----:|------| +| YES | YES | YES | NO | ## Usage From 3a2ed49dfaac4d3b5eb99c4b16cf3de3ac49c4e1 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 19:47:51 -0700 Subject: [PATCH 18/21] updated README prepared for next publish --- Cargo.toml | 2 +- README.md | 35 ++++++++++++++++++++++++++++++++--- dylink_macro/Cargo.toml | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7635048..1ccd6fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" keywords = ["ffi"] description = "Run-time dynamic linking loader utilities" repository = "https://github.com/Razordor/dylink.git" -publish = false +publish = true [workspace] members = ["dylink_macro"] diff --git a/README.md b/README.md index c03e656..108af29 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ![Crates.io](https://img.shields.io/crates/l/dylink) ![Crates.io](https://img.shields.io/crates/v/dylink) ![Crates.io](https://img.shields.io/crates/d/dylink) ![docs.rs](https://img.shields.io/docsrs/dylink) [![dylink-rs](https://github.com/Razordor/dylink/actions/workflows/rust.yml/badge.svg)](https://github.com/Razordor/dylink/actions/workflows/rust.yml) ![unsafe:yes](https://img.shields.io/badge/unsafe-yes-red) -Dylink provides a run-time dynamic linking framework for lazily evaluating shared libraries such as `.dll` files for windows -and `.so` files for unix. When functions are loaded they are evaluated through a thunk for first time calls, which loads the -function from it's respective library. Proceeding calls after initialization have no overhead or additional branching checks, +Dylink provides a run-time dynamic linking framework for lazily evaluating shared libraries such as `.dll` files. +When functions are loaded they are evaluated through a thunk for first time calls, which loads the function from +it's respective library. Proceeding calls after initialization have no overhead or additional branching checks, as the thunk is replaced by the loaded function. ---- @@ -53,6 +53,35 @@ fn main() { } ``` +## Adv. Example + +The `dylink` macro is also sophisticated enough to survive in `impl` blocks, and take advantage of the receiver argument `self`. +Unfortunately, `Self` cannot be internally inferred, so `self` without an explicit type also cannot be inferred. +The example below demonstrates how to work around these issues despite that: + +```rust +use dylink::dylink; + +#[derive(Debug, PartialEq)] +#[repr(transparent)] +struct Foo(u32); + +impl Foo { + #[dylink(name = "Kernel32.dll")] + extern "stdcall" fn GetLastError() -> Foo; + #[dylink(name = "Kernel32.dll")] + extern "stdcall" fn SetLastError(self: Foo); +} + +fn main() { + let foo = Foo(43); + unsafe { + foo.SetLastError(); + assert_eq!(Foo(43), Foo::GetLastError()); + } +} +``` + ### License Licensed under either of diff --git a/dylink_macro/Cargo.toml b/dylink_macro/Cargo.toml index cb89595..7e16f6f 100644 --- a/dylink_macro/Cargo.toml +++ b/dylink_macro/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" keywords = ["ffi", "macro", "attribute"] description = "Run-time dynamic linking loader attribute" repository = "https://github.com/Razordor/dylink.git" -publish = false +publish = true [lib] proc-macro = true From 658f414bb07e0d91481a775bc2edf51aa9e383f1 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 20:47:02 -0700 Subject: [PATCH 19/21] added Errors section --- src/lazyfn.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index 12e6497..7fe5adc 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -72,6 +72,9 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { /// for windows, or `dlsym`, and `dlopen` for unix. This function is used by the /// [dylink](dylink_macro::dylink) macro by default. /// If successful, stores address in current instance and returns a reference of the stored value. + /// + /// # Errors + /// If the library fails to link, like if it can't find the library or function, then an error is returned. /// # Example /// ```rust /// # use dylink::dylink; @@ -93,6 +96,9 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { /// Provides a generic argument to supply a user defined linker loader to load the library. /// If successful, stores address in current instance and returns a reference of the stored value. + /// + /// # Errors + /// If the library fails to link, like if it can't find the library or function, then an error is returned. pub fn try_link_with(&self) -> DylinkResult<&F> where L::Data: Send + Sync, From cb06411f787e56bcb6c1a605642f0d39ee8193de Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 21:00:51 -0700 Subject: [PATCH 20/21] fixed clippy warning --- src/lazyfn.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lazyfn.rs b/src/lazyfn.rs index 7fe5adc..bf6bac7 100644 --- a/src/lazyfn.rs +++ b/src/lazyfn.rs @@ -120,14 +120,12 @@ impl<'a, F: Copy + Sync + Send> LazyFn<'a, F> { .ok() }) .ok_or_else(|| { - if errors.len() > 1 { - error::DylinkError::ListNotLoaded( + match errors.len() { + 1 => errors[0].clone(), + 2..=usize::MAX => error::DylinkError::ListNotLoaded( errors.iter().map(|e| e.to_string() + "\n").collect(), - ) - } else if errors.len() == 1 { - errors[0].clone() - } else { - unreachable!() + ), + _ => unreachable!(), } }) } From 0df10c99f672cf8600a859ca3b5fb3f82a0a38e1 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 25 May 2023 21:33:31 -0700 Subject: [PATCH 21/21] added `FnPtr` and `DylinkResult` as public types --- src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4b0b660..c173275 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,12 +127,13 @@ static VK_INSTANCE: sync::RwLock> = sync::RwLock::new(Ve static VK_DEVICE: sync::RwLock> = sync::RwLock::new(Vec::new()); -// Used as a placeholder function pointer. This should **NEVER** be called directly, -// and promptly cast into the correct function pointer type. -pub(crate) type FnPtr = unsafe extern "system" fn() -> isize; +/// Used as a placeholder function pointer. +/// +/// This should **NEVER** be called directly, and promptly cast into the correct function pointer type. +pub type FnPtr = unsafe extern "system" fn() -> isize; -// The result of a dylink function -pub(crate) type DylinkResult = Result; +/// The result of a dylink function +pub type DylinkResult = Result; // TODO: Make the `Global` struct below public when name is picked out