Skip to content

Commit

Permalink
Add Event<T> type that can be used to implement a WinRT event (#1705)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr committed Apr 19, 2022
1 parent 1e9e81a commit a434451
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
8 changes: 3 additions & 5 deletions crates/libs/windows/src/core/agile_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ pub struct AgileReference<T>(IAgileReference, PhantomData<T>);

impl<T: Interface> AgileReference<T> {
/// Creates an agile reference to the object.
pub fn new<'a>(object: &'a T) -> Result<Self>
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<Self> {
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.
Expand Down
15 changes: 15 additions & 0 deletions crates/libs/windows/src/core/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsafe extern "system" fn() -> isize>;
#[inline]
pub unsafe fn GetLastError() -> WIN32_ERROR {
Expand Down Expand Up @@ -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);
Expand Down
254 changes: 254 additions & 0 deletions crates/libs/windows/src/core/event.rs
Original file line number Diff line number Diff line change
@@ -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<T: Interface + Clone> {
swap: Mutex<()>,
change: Mutex<()>,
delegates: Array<T>,
}

impl<T: Interface + Clone> Default for Event<T> {
fn default() -> Self {
Self::new()
}
}

impl<T: Interface + Clone> Event<T> {
/// Creates a new, empty `Event<T>`.
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<i64> {
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<F: FnMut(&T) -> 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<T: Interface + Clone> {
buffer: *mut Buffer,
len: usize,
_phantom: std::marker::PhantomData<T>,
}

impl<T: Interface + Clone> Default for Array<T> {
fn default() -> Self {
Self::new()
}
}

impl<T: Interface + Clone> Array<T> {
/// Creates a new, empty `Array<T>` with no capacity.
fn new() -> Self {
Self { buffer: std::ptr::null_mut(), len: 0, _phantom: std::marker::PhantomData }
}
/// Creates a new, empty `Array<T>` with the specified capacity.
fn with_capacity(capacity: usize) -> Result<Self> {
Ok(Self { buffer: Buffer::new(capacity * std::mem::size_of::<Delegate<T>>())?, len: 0, _phantom: std::marker::PhantomData })
}
/// Swaps the contents of two `Array<T>` 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<T>) {
unsafe {
std::ptr::write((*self.buffer).as_mut_ptr::<Delegate<T>>().add(self.len) as _, delegate);
self.len += 1;
}
}
/// Returns a slice containing of all delegates.
fn as_slice(&self) -> &[Delegate<T>] {
if self.is_empty() {
&[]
} else {
unsafe { std::slice::from_raw_parts((*self.buffer).as_ptr::<Delegate<T>>() as _, self.len) }
}
}
/// Returns a mutable slice of all delegates.
fn as_mut_slice(&mut self) -> &mut [Delegate<T>] {
if self.is_empty() {
&mut []
} else {
unsafe { std::slice::from_raw_parts_mut((*self.buffer).as_mut_ptr::<Delegate<T>>() as _, self.len) }
}
}
}

impl<T: Interface + Clone> Clone for Array<T> {
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<T: Interface + Clone> Drop for Array<T> {
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::<Buffer>() + 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<T>(&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<T>(&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<T: Interface + Clone> {
Direct(T),
Indirect(AgileReference<T>),
}

impl<T: Interface + Clone> Delegate<T> {
/// Creates a new `Delegate<T>`, containing a suitable reference to the specified delegate.
fn new(delegate: &T) -> Self {
if delegate.cast::<IAgileObject>().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<F: FnMut(&T) -> Result<()>>(&self, mut callback: F) -> Result<()> {
match self {
Self::Direct(delegate) => callback(delegate),
Self::Indirect(delegate) => callback(&delegate.resolve()?),
}
}
}
2 changes: 2 additions & 0 deletions crates/libs/windows/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down
12 changes: 12 additions & 0 deletions crates/tests/event/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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",
]
1 change: 1 addition & 0 deletions crates/tests/event/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

0 comments on commit a434451

Please sign in to comment.