diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b628a2a20..c98f09a26f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -141,6 +141,7 @@ jobs: cargo clippy -p test_does_not_return && cargo clippy -p test_enums && cargo clippy -p test_error && + cargo clippy -p test_event && cargo clippy -p test_handles && cargo clippy -p test_helpers && cargo clippy -p test_interop && diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3405c71775..56a7f92ce8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -107,6 +107,7 @@ jobs: cargo test --target ${{ matrix.target }} -p test_does_not_return && cargo test --target ${{ matrix.target }} -p test_enums && cargo test --target ${{ matrix.target }} -p test_error && + cargo test --target ${{ matrix.target }} -p test_event && cargo test --target ${{ matrix.target }} -p test_handles && cargo test --target ${{ matrix.target }} -p test_helpers && cargo test --target ${{ matrix.target }} -p test_interop && diff --git a/crates/libs/windows/src/core/agile_reference.rs b/crates/libs/windows/src/core/agile_reference.rs index 9f626c495b..d72ad2e289 100644 --- a/crates/libs/windows/src/core/agile_reference.rs +++ b/crates/libs/windows/src/core/agile_reference.rs @@ -9,11 +9,9 @@ pub struct AgileReference(IAgileReference, PhantomData); impl AgileReference { /// Creates an agile reference to the object. - pub fn new<'a>(object: &'a T) -> Result - where - &'a T: IntoParam<'a, IUnknown>, - { - unsafe { RoGetAgileReference(AGILEREFERENCE_DEFAULT, &T::IID, object).map(|reference| Self(reference, Default::default())) } + pub fn new(object: &T) -> Result { + let unknown: &IUnknown = unsafe { std::mem::transmute(object) }; + unsafe { RoGetAgileReference(AGILEREFERENCE_DEFAULT, &T::IID, unknown).map(|reference| Self(reference, Default::default())) } } /// Retrieves a proxy to the target of the `AgileReference` object that may safely be used within any thread context in which get is called. diff --git a/crates/libs/windows/src/core/bindings.rs b/crates/libs/windows/src/core/bindings.rs index 162d0b91ff..21844f98d6 100644 --- a/crates/libs/windows/src/core/bindings.rs +++ b/crates/libs/windows/src/core/bindings.rs @@ -1345,6 +1345,8 @@ pub unsafe fn CloseHandle<'a, Param0: ::windows::core::IntoParam<'a, HANDLE>>(ho pub const CO_E_NOTINITIALIZED: ::windows::core::HRESULT = ::windows::core::HRESULT(-2147221008i32); pub const E_NOINTERFACE: ::windows::core::HRESULT = ::windows::core::HRESULT(-2147467262i32); pub const E_OUTOFMEMORY: ::windows::core::HRESULT = ::windows::core::HRESULT(-2147024882i32); +pub const RPC_E_DISCONNECTED: ::windows::core::HRESULT = ::windows::core::HRESULT(-2147417848i32); +pub const JSCRIPT_E_CANTEXECUTE: ::windows::core::HRESULT = ::windows::core::HRESULT(-1996357631i32); pub type FARPROC = ::core::option::Option isize>; #[inline] pub unsafe fn GetLastError() -> WIN32_ERROR { @@ -1740,6 +1742,19 @@ pub unsafe fn SetErrorInfo<'a, Param1: ::windows::core::IntoParam<'a, IErrorInfo #[cfg(not(windows))] unimplemented!("Unsupported target OS"); } +#[inline] +pub unsafe fn EncodePointer(ptr: *const ::core::ffi::c_void) -> *mut ::core::ffi::c_void { + #[cfg(windows)] + { + #[link(name = "windows")] + extern "system" { + fn EncodePointer(ptr: *const ::core::ffi::c_void) -> *mut ::core::ffi::c_void; + } + ::core::mem::transmute(EncodePointer(::core::mem::transmute(ptr))) + } + #[cfg(not(windows))] + unimplemented!("Unsupported target OS"); +} #[repr(transparent)] #[derive(:: core :: cmp :: PartialEq, :: core :: cmp :: Eq)] pub struct FORMAT_MESSAGE_OPTIONS(pub u32); diff --git a/crates/libs/windows/src/core/event.rs b/crates/libs/windows/src/core/event.rs new file mode 100644 index 0000000000..0e0b587793 --- /dev/null +++ b/crates/libs/windows/src/core/event.rs @@ -0,0 +1,254 @@ +use super::*; +use bindings::*; +use std::sync::*; + +/// A type that you can use to declare and implement an event of a specified delegate type. +/// +/// The implementation is thread-safe and designed to avoid contention between events being +/// raised and delegates being added or removed. +pub struct Event { + swap: Mutex<()>, + change: Mutex<()>, + delegates: Array, +} + +impl Default for Event { + fn default() -> Self { + Self::new() + } +} + +impl Event { + /// Creates a new, empty `Event`. + pub fn new() -> Self { + Self { delegates: Array::new(), swap: Mutex::default(), change: Mutex::default() } + } + /// Registers a delegate with the event object. + pub fn add(&mut self, delegate: &T) -> Result { + let mut _lock_free_drop = Array::new(); + Ok({ + let change_lock = self.change.lock().unwrap(); + let mut new_delegates = Array::with_capacity(self.delegates.len() + 1)?; + for delegate in self.delegates.as_slice() { + new_delegates.push(delegate.clone()); + } + let delegate = Delegate::new(delegate); + let token = delegate.to_token(); + new_delegates.push(delegate); + + let swap_lock = self.swap.lock().unwrap(); + _lock_free_drop = self.delegates.swap(new_delegates); + token + }) + } + /// Revokes a delegate's registration from the event object. + pub fn remove(&mut self, token: i64) -> Result<()> { + let mut _lock_free_drop = Array::new(); + { + let change_lock = self.change.lock().unwrap(); + if self.delegates.is_empty() { + return Ok(()); + } + let mut capacity = self.delegates.len() - 1; + let mut new_delegates = Array::new(); + let mut removed = false; + if capacity == 0 { + if self.delegates.as_slice()[0].to_token() == token { + removed = true; + } + } else { + new_delegates = Array::with_capacity(capacity)?; + for delegate in self.delegates.as_slice() { + if !removed && delegate.to_token() == token { + removed = true; + continue; + } + if capacity == 0 { + debug_assert!(!removed); + break; + } + new_delegates.push(delegate.clone()); + capacity -= 1; + } + } + if removed { + let swap_lock = self.swap.lock().unwrap(); + _lock_free_drop = self.delegates.swap(new_delegates); + } + } + Ok(()) + } + /// Clears the event, removing all delegates. + pub fn clear(&mut self) { + let mut _lock_free_drop = Array::new(); + { + let change_lock = self.change.lock().unwrap(); + if self.delegates.is_empty() { + return; + } + let swap_lock = self.swap.lock().unwrap(); + _lock_free_drop = self.delegates.swap(Array::new()); + } + } + /// Invokes all of the event object's registered delegates with the provided callback. + pub fn call Result<()>>(&mut self, mut callback: F) -> Result<()> { + let lock_free_calls = { + let swap_lock = self.swap.lock().unwrap(); + self.delegates.clone() + }; + for delegate in lock_free_calls.as_slice() { + if let Err(error) = delegate.call(&mut callback) { + const RPC_E_SERVER_UNAVAILABLE: HRESULT = HRESULT(-2147023174); // HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) + if matches!(error.code(), RPC_E_DISCONNECTED | JSCRIPT_E_CANTEXECUTE | RPC_E_SERVER_UNAVAILABLE) { + self.remove(delegate.to_token())?; + } + } + } + Ok(()) + } +} + +/// A thread-safe reference-counted array of delegates. +struct Array { + buffer: *mut Buffer, + len: usize, + _phantom: std::marker::PhantomData, +} + +impl Default for Array { + fn default() -> Self { + Self::new() + } +} + +impl Array { + /// Creates a new, empty `Array` with no capacity. + fn new() -> Self { + Self { buffer: std::ptr::null_mut(), len: 0, _phantom: std::marker::PhantomData } + } + /// Creates a new, empty `Array` with the specified capacity. + fn with_capacity(capacity: usize) -> Result { + Ok(Self { buffer: Buffer::new(capacity * std::mem::size_of::>())?, len: 0, _phantom: std::marker::PhantomData }) + } + /// Swaps the contents of two `Array` objects. + fn swap(&mut self, mut other: Self) -> Self { + unsafe { std::ptr::swap(&mut self.buffer, &mut other.buffer) }; + std::mem::swap(&mut self.len, &mut other.len); + other + } + /// Returns `true` if the array contains no delegates. + fn is_empty(&self) -> bool { + self.len == 0 + } + /// Returns the number of delegates in the array. + fn len(&self) -> usize { + self.len + } + /// Appends a delegate to the back of the array. + fn push(&mut self, delegate: Delegate) { + unsafe { + std::ptr::write((*self.buffer).as_mut_ptr::>().add(self.len) as _, delegate); + self.len += 1; + } + } + /// Returns a slice containing of all delegates. + fn as_slice(&self) -> &[Delegate] { + if self.is_empty() { + &[] + } else { + unsafe { std::slice::from_raw_parts((*self.buffer).as_ptr::>() as _, self.len) } + } + } + /// Returns a mutable slice of all delegates. + fn as_mut_slice(&mut self) -> &mut [Delegate] { + if self.is_empty() { + &mut [] + } else { + unsafe { std::slice::from_raw_parts_mut((*self.buffer).as_mut_ptr::>() as _, self.len) } + } + } +} + +impl Clone for Array { + fn clone(&self) -> Self { + if !self.is_empty() { + unsafe { (*self.buffer).0.add_ref() }; + } + Self { buffer: self.buffer, len: self.len, _phantom: std::marker::PhantomData } + } +} + +impl Drop for Array { + fn drop(&mut self) { + unsafe { + if !self.is_empty() && (*self.buffer).0.release() == 0 { + std::ptr::drop_in_place(self.as_mut_slice()); + heap_free(self.buffer as _) + } + } + } +} + +/// A reference-counted buffer. +#[repr(C)] +struct Buffer(RefCount); + +impl Buffer { + /// Creates a new `Buffer` with the specified size in bytes. + fn new(size: usize) -> Result<*mut Buffer> { + if size == 0 { + Ok(std::ptr::null_mut()) + } else { + let alloc_size = std::mem::size_of::() + size; + let header = heap_alloc(alloc_size)? as *mut Buffer; + unsafe { + (*header).0 = RefCount::new(1); + } + Ok(header) + } + } + /// Returns a raw pointer to the buffer's contents. + fn as_ptr(&self) -> *const T { + unsafe { (self as *const Self).add(1) as *const _ } + } + /// Returns a raw mutable pointer to the buffer's contents. + fn as_mut_ptr(&mut self) -> *mut T { + unsafe { (self as *mut Self).add(1) as *mut _ } + } +} + +/// Holds either a direct or indirect reference to a delegate. A direct reference is typically +/// agile while an indirect reference is an agile wrapper. +#[derive(Clone)] +enum Delegate { + Direct(T), + Indirect(AgileReference), +} + +impl Delegate { + /// Creates a new `Delegate`, containing a suitable reference to the specified delegate. + fn new(delegate: &T) -> Self { + if delegate.cast::().is_err() { + if let Ok(delegate) = AgileReference::new(delegate) { + return Self::Indirect(delegate); + } + } + Self::Direct(delegate.clone()) + } + /// Returns an encoded token to identify the delegate. + fn to_token(&self) -> i64 { + unsafe { + match self { + Self::Direct(delegate) => EncodePointer(std::mem::transmute_copy(delegate)) as _, + Self::Indirect(delegate) => EncodePointer(std::mem::transmute_copy(delegate)) as _, + } + } + } + /// Invokes the delegates with the provided callback. + fn call Result<()>>(&self, mut callback: F) -> Result<()> { + match self { + Self::Direct(delegate) => callback(delegate), + Self::Indirect(delegate) => callback(&delegate.resolve()?), + } + } +} diff --git a/crates/libs/windows/src/core/mod.rs b/crates/libs/windows/src/core/mod.rs index 4c0c1bbbfc..544d3c9ff2 100644 --- a/crates/libs/windows/src/core/mod.rs +++ b/crates/libs/windows/src/core/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod bindings; mod compose; mod delay_load; mod error; +mod event; mod factory_cache; mod generic_factory; mod guid; @@ -37,6 +38,7 @@ pub use array::*; pub use compose::*; pub(crate) use delay_load::*; pub use error::*; +pub use event::*; #[doc(hidden)] pub use factory_cache::*; #[doc(hidden)] diff --git a/crates/tests/event/Cargo.toml b/crates/tests/event/Cargo.toml new file mode 100644 index 0000000000..02404ab358 --- /dev/null +++ b/crates/tests/event/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test_event" +version = "0.0.0" +authors = ["Microsoft"] +edition = "2021" + +[dependencies.windows] +path = "../../libs/windows" +features = [ + "Foundation", + "Win32_System_WinRT", +] diff --git a/crates/tests/event/src/lib.rs b/crates/tests/event/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/tests/event/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/tests/event/tests/tests.rs b/crates/tests/event/tests/tests.rs new file mode 100644 index 0000000000..fe27dc2530 --- /dev/null +++ b/crates/tests/event/tests/tests.rs @@ -0,0 +1,129 @@ +use std::sync::atomic::*; +use std::sync::*; +use windows::{core::*, Foundation::*}; + +#[test] +fn add_remove() -> Result<()> { + let mut event = Event::>::new(); + + // Remove a bogus event handler from an empty event source. + event.remove(-123)?; + + let check = Arc::new(AtomicI32::new(0)); + let check_sender = check.clone(); + + // Add event handler. + event.add(&EventHandler::::new(move |_, args| { + check_sender.store(*args, Ordering::Relaxed); + Ok(()) + }))?; + + // Remove a bogus event handler from a non-empty event source. + event.remove(-123)?; + + // Raise and observe event. + assert_eq!(check.load(Ordering::Relaxed), 0); + event.call(|delegate| delegate.Invoke(None, 123))?; + assert_eq!(check.load(Ordering::Relaxed), 123); + + // Remove event handler. + event.clear(); + + // Raise event without effect. + check.store(0, Ordering::Relaxed); + event.call(|delegate| delegate.Invoke(None, 123))?; + assert_eq!(check.load(Ordering::Relaxed), 0); + + Ok(()) +} + +#[test] +fn multiple() -> Result<()> { + let mut event = Event::>::new(); + + let a_check = Arc::new(AtomicI32::new(0)); + let a_check_sender = a_check.clone(); + let b_check = Arc::new(AtomicI32::new(0)); + let b_check_sender = b_check.clone(); + let c_check = Arc::new(AtomicI32::new(0)); + let c_check_sender = c_check.clone(); + + assert_eq!(a_check.load(Ordering::Relaxed), 0); + assert_eq!(b_check.load(Ordering::Relaxed), 0); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + event.call(|delegate| delegate.Invoke(None, 10))?; + assert_eq!(a_check.load(Ordering::Relaxed), 0); + assert_eq!(b_check.load(Ordering::Relaxed), 0); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + + let a_token = event.add(&EventHandler::::new(move |_, args| { + a_check_sender.store(*args, Ordering::Relaxed); + Ok(()) + }))?; + + assert_eq!(a_check.load(Ordering::Relaxed), 0); + assert_eq!(b_check.load(Ordering::Relaxed), 0); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + event.call(|delegate| delegate.Invoke(None, 10))?; + assert_eq!(a_check.load(Ordering::Relaxed), 10); + assert_eq!(b_check.load(Ordering::Relaxed), 0); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + + let b_token = event.add(&EventHandler::::new(move |_, args| { + b_check_sender.store(*args, Ordering::Relaxed); + Ok(()) + }))?; + + assert_eq!(a_check.load(Ordering::Relaxed), 10); + assert_eq!(b_check.load(Ordering::Relaxed), 0); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + event.call(|delegate| delegate.Invoke(None, 20))?; + assert_eq!(a_check.load(Ordering::Relaxed), 20); + assert_eq!(b_check.load(Ordering::Relaxed), 20); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + + let c_token = event.add(&EventHandler::::new(move |_, args| { + c_check_sender.store(*args, Ordering::Relaxed); + Ok(()) + }))?; + + assert_eq!(a_check.load(Ordering::Relaxed), 20); + assert_eq!(b_check.load(Ordering::Relaxed), 20); + assert_eq!(c_check.load(Ordering::Relaxed), 0); + event.call(|delegate| delegate.Invoke(None, 30))?; + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 30); + assert_eq!(c_check.load(Ordering::Relaxed), 30); + + event.remove(a_token)?; + + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 30); + assert_eq!(c_check.load(Ordering::Relaxed), 30); + event.call(|delegate| delegate.Invoke(None, 40))?; + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 40); + assert_eq!(c_check.load(Ordering::Relaxed), 40); + + event.remove(b_token)?; + + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 40); + assert_eq!(c_check.load(Ordering::Relaxed), 40); + event.call(|delegate| delegate.Invoke(None, 50))?; + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 40); + assert_eq!(c_check.load(Ordering::Relaxed), 50); + + event.remove(c_token)?; + + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 40); + assert_eq!(c_check.load(Ordering::Relaxed), 50); + event.call(|delegate| delegate.Invoke(None, 60))?; + assert_eq!(a_check.load(Ordering::Relaxed), 30); + assert_eq!(b_check.load(Ordering::Relaxed), 40); + assert_eq!(c_check.load(Ordering::Relaxed), 50); + + Ok(()) +} diff --git a/crates/tools/bindings/src/main.rs b/crates/tools/bindings/src/main.rs index 7f2ee459ab..7eb3df39a6 100644 --- a/crates/tools/bindings/src/main.rs +++ b/crates/tools/bindings/src/main.rs @@ -18,6 +18,8 @@ fn main() -> std::io::Result<()> { "Windows.Win32.Foundation.CO_E_NOTINITIALIZED", "Windows.Win32.Foundation.E_NOINTERFACE", "Windows.Win32.Foundation.E_OUTOFMEMORY", + "Windows.Win32.Foundation.RPC_E_DISCONNECTED", + "Windows.Win32.Foundation.JSCRIPT_E_CANTEXECUTE", "Windows.Win32.Foundation.FARPROC", "Windows.Win32.Foundation.GetLastError", "Windows.Win32.Foundation.HANDLE", @@ -36,6 +38,7 @@ fn main() -> std::io::Result<()> { "Windows.Win32.System.Com.IAgileObject", "Windows.Win32.System.Com.IErrorInfo", "Windows.Win32.System.Com.SetErrorInfo", + "Windows.Win32.System.Diagnostics.Debug.EncodePointer", "Windows.Win32.System.Diagnostics.Debug.FORMAT_MESSAGE_OPTIONS", "Windows.Win32.System.Diagnostics.Debug.FormatMessageW", "Windows.Win32.System.LibraryLoader.FreeLibrary",