From 0cce46b805abac9b857167a5f7e99e6abe5054a5 Mon Sep 17 00:00:00 2001 From: Kenny Kerr Date: Mon, 26 Aug 2024 19:52:43 -0500 Subject: [PATCH] Add async `ready` support (#3221) --- .github/workflows/clippy.yml | 2 + .github/workflows/test.yml | 6 +- crates/libs/core/src/param.rs | 9 + .../libs/windows/src/extensions/Foundation.rs | 2 + .../src/extensions/Foundation/Async.rs | 26 +++ .../src/extensions/Foundation/AsyncReady.rs | 219 ++++++++++++++++++ crates/tests/futures_impl/Cargo.toml | 17 ++ crates/tests/futures_impl/src/lib.rs | 1 + .../tests/futures_impl/tests/ready_action.rs | 62 +++++ .../tests/ready_action_progress.rs | 68 ++++++ .../futures_impl/tests/ready_operation.rs | 70 ++++++ .../tests/ready_operation_progress.rs | 72 ++++++ .../implement/tests/as_interface_param.rs | 48 ++++ 13 files changed, 600 insertions(+), 2 deletions(-) create mode 100644 crates/libs/windows/src/extensions/Foundation/AsyncReady.rs create mode 100644 crates/tests/futures_impl/Cargo.toml create mode 100644 crates/tests/futures_impl/src/lib.rs create mode 100644 crates/tests/futures_impl/tests/ready_action.rs create mode 100644 crates/tests/futures_impl/tests/ready_action_progress.rs create mode 100644 crates/tests/futures_impl/tests/ready_operation.rs create mode 100644 crates/tests/futures_impl/tests/ready_operation_progress.rs create mode 100644 crates/tests/implement/tests/as_interface_param.rs diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 060dfd85b9..87bdc1e28b 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -173,6 +173,8 @@ jobs: run: cargo clippy -p test_extensions - name: Clippy test_futures run: cargo clippy -p test_futures + - name: Clippy test_futures_impl + run: cargo clippy -p test_futures_impl - name: Clippy test_handles run: cargo clippy -p test_handles - name: Clippy test_helpers diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5004fc9d84..1f6b0225fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -199,6 +199,8 @@ jobs: run: cargo test -p test_extensions --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_futures run: cargo test -p test_futures --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Test test_futures_impl + run: cargo test -p test_futures_impl --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_handles run: cargo test -p test_handles --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_helpers @@ -253,10 +255,10 @@ jobs: run: cargo test -p test_return_handle --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_return_struct run: cargo test -p test_return_struct --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_riddle - run: cargo test -p test_riddle --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test test_riddle + run: cargo test -p test_riddle --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_standalone run: cargo test -p test_standalone --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_string_param diff --git a/crates/libs/core/src/param.rs b/crates/libs/core/src/param.rs index 9c853bed47..a37c7a7394 100644 --- a/crates/libs/core/src/param.rs +++ b/crates/libs/core/src/param.rs @@ -25,6 +25,15 @@ where } } +impl Param for InterfaceRef<'_, T> +where + T: Type, +{ + unsafe fn param(self) -> ParamValue { + ParamValue::Borrowed(transmute_copy(&self)) + } +} + impl Param for &U where T: TypeKind + Clone, diff --git a/crates/libs/windows/src/extensions/Foundation.rs b/crates/libs/windows/src/extensions/Foundation.rs index 95fc7bca6e..ae22d9a550 100644 --- a/crates/libs/windows/src/extensions/Foundation.rs +++ b/crates/libs/windows/src/extensions/Foundation.rs @@ -1,4 +1,6 @@ pub mod Async; +#[cfg(feature = "implement")] +pub mod AsyncReady; #[cfg(feature = "Foundation_Collections")] pub mod Collections; #[cfg(feature = "Foundation_Numerics")] diff --git a/crates/libs/windows/src/extensions/Foundation/Async.rs b/crates/libs/windows/src/extensions/Foundation/Async.rs index 60c620af3d..47c69c1d6d 100644 --- a/crates/libs/windows/src/extensions/Foundation/Async.rs +++ b/crates/libs/windows/src/extensions/Foundation/Async.rs @@ -19,9 +19,15 @@ pub trait Async: Interface { // The type of value produced on completion. type Output; + // The type of the delegate use for completion notification. + type CompletedHandler; + // Sets the handler or callback to invoke when execution completes. This handler can only be set once. fn set_completed(&self, handler: F) -> Result<()>; + // Calls the given handler with the current object and status. + fn invoke_completed(&self, hander: &Self::CompletedHandler, status: AsyncStatus); + // Returns the value produced on completion. This should only be called when execution completes. fn get_results(&self) -> Result; } @@ -108,6 +114,7 @@ impl Future for AsyncFuture { impl Async for IAsyncAction { type Output = (); + type CompletedHandler = AsyncActionCompletedHandler; fn set_completed(&self, handler: F) -> Result<()> { self.SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| { @@ -116,6 +123,10 @@ impl Async for IAsyncAction { })) } + fn invoke_completed(&self, handler: &Self::CompletedHandler, status: AsyncStatus) { + _ = handler.Invoke(self, status); + } + fn get_results(&self) -> Result { self.GetResults() } @@ -123,6 +134,7 @@ impl Async for IAsyncAction { impl Async for IAsyncOperation { type Output = T; + type CompletedHandler = AsyncOperationCompletedHandler; fn set_completed(&self, handler: F) -> Result<()> { self.SetCompleted(&AsyncOperationCompletedHandler::new(move |_, _| { @@ -131,6 +143,10 @@ impl Async for IAsyncOperation { })) } + fn invoke_completed(&self, handler: &Self::CompletedHandler, status: AsyncStatus) { + _ = handler.Invoke(self, status); + } + fn get_results(&self) -> Result { self.GetResults() } @@ -138,6 +154,7 @@ impl Async for IAsyncOperation { impl Async for IAsyncActionWithProgress

{ type Output = (); + type CompletedHandler = AsyncActionWithProgressCompletedHandler

; fn set_completed(&self, handler: F) -> Result<()> { self.SetCompleted(&AsyncActionWithProgressCompletedHandler::new(move |_, _| { @@ -146,6 +163,10 @@ impl Async for IAsyncActionWithProgress

{ })) } + fn invoke_completed(&self, handler: &Self::CompletedHandler, status: AsyncStatus) { + _ = handler.Invoke(self, status); + } + fn get_results(&self) -> Result { self.GetResults() } @@ -153,6 +174,7 @@ impl Async for IAsyncActionWithProgress

{ impl Async for IAsyncOperationWithProgress { type Output = T; + type CompletedHandler = AsyncOperationWithProgressCompletedHandler; fn set_completed(&self, handler: F) -> Result<()> { self.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new(move |_, _| { @@ -161,6 +183,10 @@ impl Async for IAsyncOperationWithProgress })) } + fn invoke_completed(&self, handler: &Self::CompletedHandler, status: AsyncStatus) { + _ = handler.Invoke(self, status); + } + fn get_results(&self) -> Result { self.GetResults() } diff --git a/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs b/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs new file mode 100644 index 0000000000..a45e9a0092 --- /dev/null +++ b/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs @@ -0,0 +1,219 @@ +use super::Async::Async; +use crate::{core::*, Foundation::*}; +use std::sync::atomic::{AtomicBool, Ordering}; + +struct ReadyState { + set_completed: AtomicBool, + result: Result, +} + +impl ReadyState { + fn new(result: Result) -> Self { + Self { set_completed: AtomicBool::new(false), result } + } + + fn status(&self) -> AsyncStatus { + if self.result.is_ok() { + AsyncStatus::Completed + } else { + AsyncStatus::Error + } + } + + // The "Ready" implementations don't need to store the handler since the handler is invoked immediately + // but still need to confirm that `SetCompleted` is called at most once. + fn invoke_completed(&self, sender: &T, handler: Option<&T::CompletedHandler>) -> Result<()> { + if self.set_completed.swap(true, Ordering::SeqCst) == false { + if let Some(handler) = handler { + sender.invoke_completed(handler, self.status()); + } + Ok(()) + } else { + Err(Error::from_hresult(HRESULT(0x80000018u32 as i32))) // E_ILLEGAL_DELEGATE_ASSIGNMENT + } + } + + // The `From` implementation is not used here since we don't want to transfer any error object to the calling thread. + // That happens when `GetResults` is called. + fn error_code(&self) -> Result { + Ok(match &self.result { + Ok(_) => HRESULT(0), + Err(error) => error.code(), + }) + } +} + +#[implement(IAsyncAction, IAsyncInfo)] +struct ReadyAction(ReadyState); + +#[implement(IAsyncOperation, IAsyncInfo)] +struct ReadyOperation(ReadyState>) +where + T: RuntimeType + 'static; + +#[implement(IAsyncActionWithProgress

, IAsyncInfo)] +struct ReadyActionWithProgress

(ReadyState>) +where + P: RuntimeType + 'static; + +#[implement(IAsyncOperationWithProgress, IAsyncInfo)] +struct ReadyOperationWithProgress(ReadyState>) +where + T: RuntimeType + 'static, + P: RuntimeType + 'static; + +impl IAsyncInfo_Impl for ReadyAction_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + self.0.error_code() + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for ReadyOperation_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + self.0.error_code() + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for ReadyActionWithProgress_Impl

{ + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + self.0.error_code() + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for ReadyOperationWithProgress_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + self.0.error_code() + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncAction_Impl for ReadyAction_Impl { + fn SetCompleted(&self, handler: Option<&AsyncActionCompletedHandler>) -> Result<()> { + self.0.invoke_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result { + Err(Error::empty()) + } + fn GetResults(&self) -> Result<()> { + self.0.result.clone() + } +} + +impl IAsyncOperation_Impl for ReadyOperation_Impl { + fn SetCompleted(&self, handler: Option<&AsyncOperationCompletedHandler>) -> Result<()> { + self.0.invoke_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result { + self.0.result.clone() + } +} + +impl IAsyncActionWithProgress_Impl

for ReadyActionWithProgress_Impl

{ + fn SetCompleted(&self, handler: Option<&AsyncActionWithProgressCompletedHandler

>) -> Result<()> { + self.0.invoke_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result<()> { + self.0.result.clone() + } + fn SetProgress(&self, _: Option<&AsyncActionProgressHandler

>) -> Result<()> { + Ok(()) + } + fn Progress(&self) -> Result> { + Err(Error::empty()) + } +} + +impl IAsyncOperationWithProgress_Impl for ReadyOperationWithProgress_Impl { + fn SetCompleted(&self, handler: Option<&AsyncOperationWithProgressCompletedHandler>) -> Result<()> { + self.0.invoke_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result { + self.0.result.clone() + } + fn SetProgress(&self, _: Option<&AsyncOperationProgressHandler>) -> Result<()> { + Ok(()) + } + fn Progress(&self) -> Result> { + Err(Error::empty()) + } +} + +impl IAsyncAction { + pub fn ready(result: Result<()>) -> Self { + ReadyAction(ReadyState::new(result)).into() + } +} + +impl IAsyncOperation { + pub fn ready(result: Result) -> Self { + ReadyOperation(ReadyState::new(result)).into() + } +} + +impl IAsyncActionWithProgress

{ + pub fn ready(result: Result<()>) -> Self { + ReadyActionWithProgress(ReadyState::new(result)).into() + } +} + +impl IAsyncOperationWithProgress { + pub fn ready(result: Result) -> Self { + ReadyOperationWithProgress(ReadyState::new(result)).into() + } +} diff --git a/crates/tests/futures_impl/Cargo.toml b/crates/tests/futures_impl/Cargo.toml new file mode 100644 index 0000000000..1380c81a50 --- /dev/null +++ b/crates/tests/futures_impl/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "test_futures_impl" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +doc = false +doctest = false + +[dependencies.windows] +path = "../../libs/windows" +features = [ + "implement", + "Foundation", + "Win32_Foundation", +] diff --git a/crates/tests/futures_impl/src/lib.rs b/crates/tests/futures_impl/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/tests/futures_impl/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/tests/futures_impl/tests/ready_action.rs b/crates/tests/futures_impl/tests/ready_action.rs new file mode 100644 index 0000000000..a45a4685ac --- /dev/null +++ b/crates/tests/futures_impl/tests/ready_action.rs @@ -0,0 +1,62 @@ +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn ok() -> Result<()> { + let a = IAsyncAction::ready(Ok(())); + let _: IAsyncInfo = a.cast()?; + assert_eq!(a.Id()?, 1); + assert_eq!(a.Status()?, AsyncStatus::Completed); + assert_eq!(a.ErrorCode()?, HRESULT(0)); + assert_eq!(a.Completed(), Err(Error::empty())); + assert_eq!(a.GetResults(), Ok(())); + a.Cancel()?; + a.Close()?; + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored + }))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| { Ok(()) })) + .unwrap_err() + .code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + IAsyncAction::ready(Ok(())).get()?; + Ok(()) +} + +#[test] +fn err() -> Result<()> { + let a = IAsyncAction::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + assert_eq!(a.Status()?, AsyncStatus::Error); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + let error = a.GetResults().unwrap_err(); + assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(error.message(), "async"); + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Error); + send.send(()).unwrap(); + Ok(()) + }))?; + + recv.recv().unwrap(); + Ok(()) +} diff --git a/crates/tests/futures_impl/tests/ready_action_progress.rs b/crates/tests/futures_impl/tests/ready_action_progress.rs new file mode 100644 index 0000000000..a683ae14d6 --- /dev/null +++ b/crates/tests/futures_impl/tests/ready_action_progress.rs @@ -0,0 +1,68 @@ +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn ok() -> Result<()> { + let a = IAsyncActionWithProgress::::ready(Ok(())); + let _: IAsyncInfo = a.cast()?; + assert_eq!(a.Id()?, 1); + assert_eq!(a.Status()?, AsyncStatus::Completed); + assert_eq!(a.ErrorCode()?, HRESULT(0)); + assert_eq!(a.Completed(), Err(Error::empty())); + assert_eq!(a.GetResults(), Ok(())); + a.Cancel()?; + a.Close()?; + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |_, _| { Ok(()) } + )) + .unwrap_err() + .code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + IAsyncActionWithProgress::::ready(Ok(())).get()?; + Ok(()) +} + +#[test] +fn err() -> Result<()> { + let a = IAsyncActionWithProgress::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + assert_eq!(a.Status()?, AsyncStatus::Error); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + let error = a.GetResults().unwrap_err(); + assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(error.message(), "async"); + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Error); + send.send(()).unwrap(); + Ok(()) + }, + ))?; + + recv.recv().unwrap(); + Ok(()) +} diff --git a/crates/tests/futures_impl/tests/ready_operation.rs b/crates/tests/futures_impl/tests/ready_operation.rs new file mode 100644 index 0000000000..31716a4fa0 --- /dev/null +++ b/crates/tests/futures_impl/tests/ready_operation.rs @@ -0,0 +1,70 @@ +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn ok() -> Result<()> { + let a = IAsyncOperation::ready(Ok(123)); + let _: IAsyncInfo = a.cast()?; + assert_eq!(a.Id()?, 1); + assert_eq!(a.Status()?, AsyncStatus::Completed); + assert_eq!(a.ErrorCode()?, HRESULT(0)); + assert_eq!(a.Completed(), Err(Error::empty())); + assert_eq!(a.GetResults(), Ok(123)); + a.Cancel()?; + a.Close()?; + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(&AsyncOperationCompletedHandler::new(move |_, _| { Ok(()) })) + .unwrap_err() + .code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!( + IAsyncOperation::ready(Ok(HSTRING::from("hello"))).get()?, + "hello" + ); + + Ok(()) +} + +#[test] +fn err() -> Result<()> { + let a = IAsyncOperation::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + assert_eq!(a.Status()?, AsyncStatus::Error); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + let error = a.GetResults().unwrap_err(); + assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(error.message(), "async"); + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Error); + send.send(()).unwrap(); + Ok(()) + }, + ))?; + + recv.recv().unwrap(); + Ok(()) +} diff --git a/crates/tests/futures_impl/tests/ready_operation_progress.rs b/crates/tests/futures_impl/tests/ready_operation_progress.rs new file mode 100644 index 0000000000..6c52df359d --- /dev/null +++ b/crates/tests/futures_impl/tests/ready_operation_progress.rs @@ -0,0 +1,72 @@ +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn ok() -> Result<()> { + let a = IAsyncOperationWithProgress::::ready(Ok(123)); + let _: IAsyncInfo = a.cast()?; + assert_eq!(a.Id()?, 1); + assert_eq!(a.Status()?, AsyncStatus::Completed); + assert_eq!(a.ErrorCode()?, HRESULT(0)); + assert_eq!(a.Completed(), Err(Error::empty())); + assert_eq!(a.GetResults(), Ok(123)); + a.Cancel()?; + a.Close()?; + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |_, _| { Ok(()) } + )) + .unwrap_err() + .code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!( + IAsyncOperationWithProgress::::ready(Ok(HSTRING::from("hello"))).get()?, + "hello" + ); + + Ok(()) +} + +#[test] +fn err() -> Result<()> { + let a = IAsyncOperationWithProgress::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + assert_eq!(a.Status()?, AsyncStatus::Error); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + let error = a.GetResults().unwrap_err(); + assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(error.message(), "async"); + + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Error); + send.send(()).unwrap(); + Ok(()) + }, + ))?; + + recv.recv().unwrap(); + Ok(()) +} diff --git a/crates/tests/implement/tests/as_interface_param.rs b/crates/tests/implement/tests/as_interface_param.rs new file mode 100644 index 0000000000..a68a7d1bd3 --- /dev/null +++ b/crates/tests/implement/tests/as_interface_param.rs @@ -0,0 +1,48 @@ +use windows::{core::*, Foundation::*}; + +#[implement(IAsyncAction)] +struct Async; + +impl IAsyncAction_Impl for Async_Impl { + fn SetCompleted(&self, handler: Option<&AsyncActionCompletedHandler>) -> Result<()> { + // This validates that `as_interface` may be used to call a bindgen-produced method expecting a `Param` argument. + handler + .unwrap() + .Invoke(self.as_interface(), AsyncStatus::Completed) + } + fn Completed(&self) -> Result { + todo!(); + } + fn GetResults(&self) -> Result<()> { + todo!(); + } +} + +impl IAsyncInfo_Impl for Async_Impl { + fn Id(&self) -> Result { + todo!(); + } + fn Status(&self) -> Result { + todo!(); + } + fn ErrorCode(&self) -> Result { + todo!(); + } + fn Cancel(&self) -> Result<()> { + todo!(); + } + fn Close(&self) -> Result<()> { + todo!(); + } +} + +#[test] +fn test() -> Result<()> { + let a: IAsyncAction = Async.into(); + let a_clone = a.clone(); + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + assert_eq!(&a_clone, sender.unwrap()); + assert_eq!(status, AsyncStatus::Completed); + Ok(()) + })) +}