diff --git a/crates/libs/bindgen/src/config/mod.rs b/crates/libs/bindgen/src/config/mod.rs index 41d9766a61..203e3e9450 100644 --- a/crates/libs/bindgen/src/config/mod.rs +++ b/crates/libs/bindgen/src/config/mod.rs @@ -27,6 +27,7 @@ pub struct Config<'a> { pub implement: bool, pub implements: &'a Implements, pub noexcept: bool, + pub middleware: bool, pub specific_deps: bool, pub derive: &'a Derive, pub link: &'a str, diff --git a/crates/libs/bindgen/src/lib.rs b/crates/libs/bindgen/src/lib.rs index e63adf8184..cd88a5dfe7 100644 --- a/crates/libs/bindgen/src/lib.rs +++ b/crates/libs/bindgen/src/lib.rs @@ -90,6 +90,7 @@ pub fn builder() -> Bindgen { /// | `--implement` | Includes implementation traits for WinRT interfaces. | /// | `--implements` | Includes implementation traits for the listed types only. | /// | `--noexcept` | Assumes all WinRT methods do not raise exceptions. | +/// | `--middleware` | Emits compact bodies that tail-call into `windows_core::imp` helpers. | /// | `--link` | Overrides the default `windows-link` implementation for system calls. | /// /// @@ -308,6 +309,40 @@ pub fn builder() -> Bindgen { /// need fine-grained dependency management and want to minimize your dependency tree, this option /// provides that flexibility. /// +/// # `--middleware` +/// +/// The `--middleware` option emits compact `pub fn` bodies that tail-call into a small set +/// of generic helpers in `windows_core::imp` (`call_in`, `call_in_out`, `call_compose`) +/// instead of expanding the full +/// `unsafe { let mut result__ = zeroed(); (vtable(self).M)(...).and_then(...) }` shape +/// inline at every call site. Public signatures are byte-identical; only the body shape +/// changes. +/// +/// The helpers cover every WinRT receiver shape that `bindgen` produces for +/// `Result`-returning methods: +/// +/// * `Default` arm — `&self` instance methods (`call_in` / `call_in_out`). +/// * `None` / `Base` arm — instance methods reached via a `cast::<…>` prelude +/// (`call_in` / `call_in_out`, with `this` as receiver). +/// * `Static` arm — class statics dispatched through `Self::IFoo(|this| { … })` +/// (`call_in` / `call_in_out`, with `this` as receiver). +/// * `Composable` primary entry — non-aggregating constructors (`call_in_out`, with the +/// two outer/inner null placeholder slots spliced into the helper's argument tail to +/// keep the ABI byte-identical). +/// * `Composable` `_compose` aggregating entry — `call_compose`, which performs the +/// `Compose::compose` / vtable dispatch / `let _ = &derived__;` keep-alive / +/// `T::from_abi` sequence in one place. +/// +/// Shapes that don't fit a helper (`noexcept`-style infallible returns, WinRT array +/// returns, `CloneType` returns such as `HSTRING`, bare `Type::Generic(_)` returns where +/// the impl block does not bound `>::Abi: Default`, the C++/Win32 surface) +/// fall back to today's inline expansion automatically, so the flag is always safe to +/// enable. +/// +/// This is intended for "middleware" / "reactor" callers (such as `windows-reactor-rs`) that +/// forward calls through curated WinRT surfaces and want to minimize `bindings.rs` byte size, +/// LLVM IR size, and `MIR_borrow_checking` time. Default emission is unchanged. +/// #[track_caller] #[must_use] pub fn bindgen(args: I) -> Warnings @@ -370,6 +405,9 @@ where "--noexcept" => { builder.noexcept(); } + "--middleware" => { + builder.middleware(); + } "--specific-deps" => { builder.specific_deps(); } @@ -452,6 +490,7 @@ pub struct Bindgen { package: bool, implement: bool, noexcept: bool, + middleware: bool, specific_deps: bool, sys: bool, typedef: bool, @@ -641,6 +680,25 @@ impl Bindgen { self } + /// Emit compact, helper-based call-site bodies for the shapes that fit. + /// + /// When set, generated WinRT method bodies that match the canonical + /// shape (interface return or copyable scalar return, no winrt array, + /// `Result`-typed signature, `Default` interface kind) become a single + /// tail call into `windows_core::imp::call_in_out` / `call_in`. Public + /// signatures are byte-identical; only the body shape changes. Shapes + /// that don't fit a helper fall back to today's inline expansion, so + /// this flag is always safe to enable. + /// + /// Default emission (without this flag) is unchanged. This is intended + /// for "middleware" / "reactor" callers that need to minimize + /// `bindings.rs` size and codegen cost; see the project documentation + /// for guidance. + pub fn middleware(&mut self) -> &mut Self { + self.middleware = true; + self + } + /// Use specific crate dependencies rather than `windows-core`. pub fn specific_deps(&mut self) -> &mut Self { self.specific_deps = true; @@ -824,6 +882,7 @@ impl Bindgen { implement: self.implement, implements: &implements, noexcept: self.noexcept, + middleware: self.middleware, specific_deps: self.specific_deps, link, warnings: &warnings, diff --git a/crates/libs/bindgen/src/types/method.rs b/crates/libs/bindgen/src/types/method.rs index edf85c208e..11ace612e1 100644 --- a/crates/libs/bindgen/src/types/method.rs +++ b/crates/libs/bindgen/src/types/method.rs @@ -375,6 +375,20 @@ impl Method { } }; + // typed_args without the trailing return out-pointer; used by middleware-mode + // helper emission where the helper supplies the out-pointer slot itself. The + // trailing `,` is preserved when non-empty so it splices cleanly before the + // helper-supplied `result__` argument. For Composable's primary (non-aggregating) + // entry the two null placeholder slots (outer + inner) are spliced in here so + // the helper-emitted call site retains the same ABI. + let typed_args_only: TokenStream = if kind == InterfaceKind::Composable { + quote! { #(#typed_args,)* core::ptr::null_mut(), &mut core::ptr::null_mut(), } + } else if typed_args.is_empty() { + TokenStream::new() + } else { + quote! { #(#typed_args,)* } + }; + let args = if kind == InterfaceKind::Composable { // Composable factory methods take the `outer` controlling unknown and the // out-pointer for the `inner` non-delegating object as their last two @@ -537,9 +551,73 @@ impl Method { // given `args` token stream. This is wrapped in a closure so we can build a parallel // `_compose` variant for composable factory methods without duplicating the // return-type plumbing below. - let build_vcall = |args: &TokenStream| -> TokenStream { + // + // `typed_args_only` contains the in-parameter expressions without the trailing + // out-pointer (or `null_mut()` placeholders for composable factories). When + // middleware mode is enabled and the return shape fits one of the + // `windows_core::imp::call_*` helpers, we emit a tail call into the helper using + // `typed_args_only` so the helper supplies the `result__` slot itself. Otherwise + // we fall back to the existing inline expansion using `args` (which already + // splices in the trailing out-pointer). + let build_vcall = |args: &TokenStream, typed_args_only: &TokenStream| -> TokenStream { let vcall = quote! { (windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #args) }; + // Middleware-mode helper emission. Only applies when: + // * `--middleware` is set, + // * the method does not use `noexcept`-style infallible return, + // * the return is not a WinRT array. + // + // The helper covers all four `kind`s that go through `build_vcall`: + // + // * `Default` (`#receiver = self`), + // * `None` / `Base` (`#receiver = this`, after a `cast::<…>` prelude), + // * `Static` (`#receiver = this`, inside the `Self::IFoo(|this| { … })` + // factory closure), + // * `Composable` primary (non-aggregating) entry (`#receiver = this`, + // inside the factory closure; the two outer/inner null placeholder + // slots are spliced into `typed_args_only` above). + // + // The `Composable` aggregating `_compose` variant goes through a + // dedicated `call_compose` helper emitted in the `InterfaceKind::Composable` + // arm below — it is *not* produced by this closure. + // + // It covers `Result` interface returns (Abi = *mut c_void) and + // `Result` copyable returns (Abi = T) via the `Default`-bounded + // `>::Abi` slot in `call_in_out`. The "transmute" branch + // (CloneType such as HSTRING, where Abi = MaybeUninit) is intentionally + // not covered — it falls back to the inline expansion below. + let middleware_eligible = + config.middleware && !noexcept && !self.signature.return_type.is_winrt_array(); + + if middleware_eligible { + match &self.signature.return_type { + Type::Void => { + return quote! { + windows_core::imp::call_in(#receiver, |this__| + (windows_core::Interface::vtable(#receiver).#vname)(this__, #typed_args_only)) + }; + } + // `Type::Generic(_)` (a bare generic parameter such as `TResult`) + // is excluded: the helper requires `>::Abi: Default`, + // which isn't implied by the typical `T: RuntimeType + 'static` + // bound the generated impl block carries. Fall back to inline. + Type::Generic(_) => {} + return_type + if return_type.is_convertible() + || return_type.is_copyable(config.reader) => + { + // `call_in_out` requires `>::Abi: Default`. This + // holds for interface (`Abi = *mut c_void`) and copyable + // (`Abi = T` for primitives/enums/BOOL/HANDLE) returns. + return quote! { + windows_core::imp::call_in_out(#receiver, |this__, result__| + (windows_core::Interface::vtable(#receiver).#vname)(this__, #typed_args_only result__)) + }; + } + _ => {} // CloneType etc. — fall through to inline expansion. + } + } + match &self.signature.return_type { Type::Void => { if noexcept { @@ -598,7 +676,7 @@ impl Method { } }; - let vcall = build_vcall(&args); + let vcall = build_vcall(&args, &typed_args_only); match kind { InterfaceKind::Default => quote! { @@ -663,22 +741,49 @@ impl Method { // left with a dangling reference into the inner's vtables. let interface_name = to_ident(trim_tick(interface.unwrap().def.name())); + // `_compose` body. In `--middleware` mode and when the return shape + // fits (interface or copyable scalar — see same eligibility rules as + // the `Default` arm), we collapse the body into a single tail call + // through `windows_core::imp::call_compose`, which performs the + // `Compose::compose` / vtable dispatch / `let _ = &derived__;` + // keep-alive / `from_abi` sequence in one place. + // + // Otherwise (or when middleware mode is off), emit the original + // inline expansion below. + let compose_eligible = config.middleware + && !noexcept + && !self.signature.return_type.is_winrt_array() + && !matches!(&self.signature.return_type, Type::Generic(_)) + && (self.signature.return_type.is_convertible() + || self.signature.return_type.is_copyable(config.reader)); + + let compose_body = if compose_eligible { + quote! { + windows_core::imp::call_compose(this, compose, |this__, derived__, base__, result__| + (windows_core::Interface::vtable(this).#vname)(this__, #(#typed_args,)* derived__, base__, result__)) + } + } else { + quote! { + let (derived__, base__) = windows_core::Compose::compose(compose); + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #compose_args).ok()?; + // Suppress unused-variable warning: `derived__` is held alive + // for the duration of the factory call (so the factory can + // store a back-pointer to the outer in the inner) and then + // dropped at scope end — its sole owning ref is replaced by + // the delegating ref baked into `result__`. + let _ = &derived__; + windows_core::Type::from_abi(result__) + } + }; + quote! { pub fn #name<#(#generics,)*>(#(#params)*) #return_type #where_clause { Self::#interface_name(|this| unsafe { #vcall }) } pub fn #name_compose<#(#generics,)* T>(#(#params)* compose: T) #return_type #where_clause_compose { Self::#interface_name(|this| unsafe { - let (derived__, base__) = windows_core::Compose::compose(compose); - let mut result__ = core::mem::zeroed(); - (windows_core::Interface::vtable(#receiver).#vname)(windows_core::Interface::as_raw(#receiver), #compose_args).ok()?; - // Suppress unused-variable warning: `derived__` is held alive - // for the duration of the factory call (so the factory can - // store a back-pointer to the outer in the inner) and then - // dropped at scope end — its sole owning ref is replaced by - // the delegating ref baked into `result__`. - let _ = &derived__; - windows_core::Type::from_abi(result__) + #compose_body }) } } diff --git a/crates/libs/core/src/imp/call.rs b/crates/libs/core/src/imp/call.rs new file mode 100644 index 0000000000..a98342ebcc --- /dev/null +++ b/crates/libs/core/src/imp/call.rs @@ -0,0 +1,288 @@ +//! Generic call-site helpers used by `--middleware`-mode bindings. +//! +//! These helpers collapse the repeated `unsafe { let mut result__ = zeroed(); (vtable(self).M)(...).and_then(|| Type::from_abi(result__)) }` +//! shape that bindgen otherwise emits inline for every WinRT method. Each +//! generated `pub fn` becomes a single tail call into one of these helpers, +//! which shrinks both LLVM IR size and the borrow-checker workload (number of +//! `unsafe` blocks) without changing the public signature. +//! +//! The helpers are deliberately minimal: they cover only the shapes whose +//! generic bounds work cleanly. Shapes that don't fit (e.g. `MaybeUninit` +//! `CloneType` returns, WinRT arrays, factory closures) fall back to the +//! pre-existing inline expansion. +//! +//! These are `#[doc(hidden)]` and considered an implementation detail of the +//! generated bindings. They live in `windows_core::imp` because bindgen-emitted +//! code can already address that path. +//! +//! # Safety +//! +//! Each helper is `unsafe` because the closure it accepts performs a raw +//! vtable call. Callers (i.e. bindgen-emitted bodies) must ensure: +//! +//! * `this` is a valid interface pointer of type `I`. +//! * The closure invokes the correct vtable slot with arguments matching its +//! ABI signature, and stores into the supplied out-pointer on success. +//! * For [`call_in_out`], the out-pointer is left in a state from which +//! `T::from_abi` can correctly reconstruct an owned `T` on success. + +use crate::{Compose, Interface, Result, Type, HRESULT}; +use core::ffi::c_void; + +/// Helper for the canonical "one out-param, return `Result`" call shape: +/// +/// ```ignore +/// let mut result__ = core::mem::zeroed(); +/// (Interface::vtable(self).M)(Interface::as_raw(self), …, &mut result__) +/// .and_then(|| Type::from_abi(result__)) +/// ``` +/// +/// The closure receives the raw `this` pointer and the out-pointer slot; +/// callers are responsible for invoking the correct vtable slot with any +/// additional in-parameters. +/// +/// Works for any `T: Type` whose `Abi` type is `Default` — i.e. interface +/// types (`Abi = *mut c_void`) and copyable scalar / enum / `BOOL`-class types +/// (`Abi = T`). For `CloneType` returns whose `Abi = MaybeUninit` callers +/// should fall back to the inline expansion (the helper would be unsound or +/// would need a per-shape variant). +/// +/// # Safety +/// +/// See module-level safety notes. +#[doc(hidden)] +#[inline] +pub unsafe fn call_in_out(this: &I, f: F) -> Result +where + I: Interface, + T: Type, + >::Abi: Default, + F: FnOnce(*mut c_void, *mut >::Abi) -> HRESULT, +{ + let mut result__ = <>::Abi as Default>::default(); + f(Interface::as_raw(this), &mut result__).and_then(|| unsafe { T::from_abi(result__) }) +} + +/// Helper for the "no out-param, return `Result<()>`" call shape: +/// +/// ```ignore +/// (Interface::vtable(self).M)(Interface::as_raw(self), …).ok() +/// ``` +/// +/// The closure receives the raw `this` pointer; callers are responsible for +/// invoking the correct vtable slot with any in-parameters. +/// +/// # Safety +/// +/// See module-level safety notes. +#[doc(hidden)] +#[inline] +pub unsafe fn call_in(this: &I, f: F) -> Result<()> +where + I: Interface, + F: FnOnce(*mut c_void) -> HRESULT, +{ + f(Interface::as_raw(this)).ok() +} + +/// Helper for the WinRT composable factory call shape. +/// +/// Composable factory methods take an "outer" controlling [`IInspectable`] +/// (used to delegate `QueryInterface` back to the aggregating object) and an +/// out-pointer for the "inner" non-delegating `IInspectable`. After a +/// successful call, the inner is owned through the outer's `ComposeBase` slot +/// and the returned default-interface pointer's `IUnknown` methods delegate +/// back to the outer's controlling unknown. +/// +/// The closure receives four raw pointers — `this__`, `derived__` (outer), +/// `base__` (writeback slot for the inner), and `result__` (out-pointer for +/// the aggregated default interface) — and is expected to invoke the correct +/// composable factory vtable slot with them, splicing in any additional +/// in-parameters before `derived__`. +/// +/// The local `derived__: IInspectable` returned by [`Compose::compose`] is +/// kept alive across the vtable call (so the runtime can store a back-pointer +/// to the outer in the inner) and dropped only after `T::from_abi` has run on +/// success — its sole owning ref is replaced by the delegating ref baked into +/// `result__`. Dropping it earlier would be unsound. +/// +/// # Safety +/// +/// In addition to the module-level safety notes, the closure must invoke a +/// composable factory vtable slot whose ABI matches the +/// `(this, …, outer, &mut inner, &mut result)` shape and store the aggregated +/// default-interface pointer into `result__` on success. +#[doc(hidden)] +#[inline] +pub unsafe fn call_compose(this: &I, compose: P, f: F) -> Result +where + I: Interface, + T: Type, + >::Abi: Default, + P: Compose, + F: FnOnce(*mut c_void, *mut c_void, *mut *mut c_void, *mut >::Abi) -> HRESULT, +{ + let (derived__, base__) = unsafe { Compose::compose(compose) }; + let mut result__ = <>::Abi as Default>::default(); + f( + Interface::as_raw(this), + unsafe { core::mem::transmute_copy(&derived__) }, + base__ as *mut _ as _, + &mut result__, + ) + .ok()?; + // Suppress unused-variable warning: `derived__` is held alive for the + // duration of the factory call (so the factory can store a back-pointer + // to the outer in the inner) and then dropped at scope end — its sole + // owning ref is replaced by the delegating ref baked into `result__`. + // Critical: this MUST happen *after* the success check and before + // `from_abi` consumes `result__`, so the outer's lifetime spans the + // entire vtable call. Dropping earlier is unsound. + let _ = &derived__; + unsafe { T::from_abi(result__) } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{implement, IUnknown}; + + // A trivial implementer to exercise the helpers without depending on the + // OS. The helpers themselves are agnostic to the underlying COM impl. + #[implement()] + struct Object; + + #[test] + fn call_in_returns_ok_on_success() { + let object: IUnknown = Object.into(); + // Closure that returns S_OK and ignores the raw pointer. + let result = unsafe { call_in(&object, |_| HRESULT(0)) }; + assert!(result.is_ok()); + } + + #[test] + fn call_in_propagates_failure() { + let object: IUnknown = Object.into(); + let result = unsafe { call_in(&object, |_| HRESULT(0x80004005u32 as i32)) }; + assert!(result.is_err()); + } + + #[test] + fn call_in_out_copy_scalar() { + // u32 is CopyType; Abi = u32; Default = 0. + let object: IUnknown = Object.into(); + let result: Result = unsafe { + call_in_out(&object, |_, out| { + *out = 42; + HRESULT(0) + }) + }; + assert_eq!(result.unwrap(), 42); + } + + #[test] + fn call_in_out_propagates_failure() { + let object: IUnknown = Object.into(); + let result: Result = + unsafe { call_in_out(&object, |_, _| HRESULT(0x80004005u32 as i32)) }; + assert!(result.is_err()); + } + + #[test] + fn call_in_out_interface_handles_null() { + // For interface returns, Abi = *mut c_void; from_abi returns Err on null. + let object: IUnknown = Object.into(); + let result: Result = unsafe { + call_in_out(&object, |_, _| { + // Leave out as null (Default for *mut c_void). + HRESULT(0) + }) + }; + assert!(result.is_err()); + } + + // Refcount-tracking implementation type used by the `call_compose` tests + // below. `Drop` decrements `ALIVE` so we can assert on live-object counts + // across the helper's `compose` → vtable-call → `from_abi` sequence. + // + // The tests share the `ALIVE` counter, so they must not run concurrently + // (cargo test runs tests in parallel by default). `LOCK` serializes them. + static ALIVE: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); + static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + #[implement()] + struct CountedComposable; + + impl CountedComposable { + fn new() -> Self { + ALIVE.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + CountedComposable + } + } + + impl Drop for CountedComposable { + fn drop(&mut self) { + ALIVE.fetch_sub(1, core::sync::atomic::Ordering::SeqCst); + } + } + + #[test] + fn call_compose_observes_derived_alive_in_closure_and_drops_after_return() { + let _guard = LOCK.lock().unwrap_or_else(|e| e.into_inner()); + // Reset; tests in `cargo test` may run in parallel but each test + // creates its own `CountedComposable` and these atomics nest. + let before = ALIVE.load(core::sync::atomic::Ordering::SeqCst); + + let counted = CountedComposable::new(); + assert_eq!(ALIVE.load(core::sync::atomic::Ordering::SeqCst), before + 1); + + let factory: IUnknown = Object.into(); + let result: Result = unsafe { + call_compose( + &factory, + counted, + |_this, derived__, base__, _result_out| { + // While the closure runs, the heap-allocated outer is alive. + // `compose` moved `counted` into an `IInspectable`, which + // owns the only ref; `derived__` is a borrowed raw view of + // it via `transmute_copy`. + assert!(!derived__.is_null()); + assert!(!base__.is_null()); + assert_eq!(ALIVE.load(core::sync::atomic::Ordering::SeqCst), before + 1); + // Leave result__ null so the helper's `from_abi` fails on + // the success path; we still want the keep-alive guard to + // hold derived__ across the call. Use a successful HRESULT + // so we exercise the success path through `from_abi`. + HRESULT(0) + }, + ) + }; + + // `from_abi(null)` is the ultimate result. + assert!(result.is_err()); + + // After `call_compose` returns, the local derived__ has dropped, the + // IInspectable's refcount has hit zero, and `CountedComposable::drop` + // has run. + assert_eq!(ALIVE.load(core::sync::atomic::Ordering::SeqCst), before); + } + + #[test] + fn call_compose_propagates_factory_failure() { + let _guard = LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let before = ALIVE.load(core::sync::atomic::Ordering::SeqCst); + let counted = CountedComposable::new(); + let factory: IUnknown = Object.into(); + + let result: Result = unsafe { + call_compose(&factory, counted, |_, _, _, _| { + HRESULT(0x80004005u32 as i32) + }) + }; + + assert!(result.is_err()); + // Whether the helper bailed before or after `from_abi`, `derived__` + // must have been dropped by the time we get here. + assert_eq!(ALIVE.load(core::sync::atomic::Ordering::SeqCst), before); + } +} diff --git a/crates/libs/core/src/imp/mod.rs b/crates/libs/core/src/imp/mod.rs index d77baf13ab..14094cba78 100644 --- a/crates/libs/core/src/imp/mod.rs +++ b/crates/libs/core/src/imp/mod.rs @@ -3,6 +3,7 @@ include!("windows.rs"); mod agile; mod bindings; +mod call; mod can_into; mod com_bindings; mod delegate_box; @@ -12,6 +13,7 @@ mod weak_ref_count; pub use agile::*; pub(crate) use bindings::*; +pub use call::*; pub use can_into::*; pub use com_bindings::*; pub use delegate_box::*; diff --git a/crates/tests/fixtures/harness/data/bindgen/middleware/expected.rs b/crates/tests/fixtures/harness/data/bindgen/middleware/expected.rs new file mode 100644 index 0000000000..0ca7ece565 --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/middleware/expected.rs @@ -0,0 +1,404 @@ +pub mod Test { + #[repr(transparent)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct Foo(windows_core::IUnknown); + windows_core::imp::interface_hierarchy!( + Foo, + windows_core::IUnknown, + windows_core::IInspectable, + IFoo + ); + windows_core::imp::required_hierarchy!(Foo, IFooFactory, IFooStatics); + impl Foo { + pub fn CreateInstance(name: i32) -> windows_result::Result { + Self::IFooFactory(|this| unsafe { + windows_core::imp::call_in_out(this, |this__, result__| { + (windows_core::Interface::vtable(this).CreateInstance)( + this__, + name, + core::ptr::null_mut(), + &mut core::ptr::null_mut(), + result__, + ) + }) + }) + } + pub fn CreateInstance_compose(name: i32, compose: T) -> windows_result::Result + where + T: windows_core::Compose, + { + Self::IFooFactory(|this| unsafe { + windows_core::imp::call_compose( + this, + compose, + |this__, derived__, base__, result__| { + (windows_core::Interface::vtable(this).CreateInstance)( + this__, name, derived__, base__, result__, + ) + }, + ) + }) + } + pub fn Reset() -> windows_result::Result<()> { + Self::IFooStatics(|this| unsafe { + windows_core::imp::call_in(this, |this__| { + (windows_core::Interface::vtable(this).Reset)(this__) + }) + }) + } + pub fn Current() -> windows_result::Result { + Self::IFooStatics(|this| unsafe { + windows_core::imp::call_in_out(this, |this__, result__| { + (windows_core::Interface::vtable(this).Current)(this__, result__) + }) + }) + } + fn IFooFactory windows_result::Result>( + callback: F, + ) -> windows_result::Result { + static SHARED: windows_core::imp::FactoryCache = + windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } + fn IFooStatics windows_result::Result>( + callback: F, + ) -> windows_result::Result { + static SHARED: windows_core::imp::FactoryCache = + windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } + } + impl windows_core::RuntimeType for Foo { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_class::(); + } + unsafe impl windows_core::Interface for Foo { + type Vtable = ::Vtable; + const IID: windows_core::GUID = ::IID; + } + impl windows_core::RuntimeName for Foo { + const NAME: &'static str = "Test.Foo"; + } + windows_core::imp::define_interface!(IFoo, IFoo_Vtbl, 0x347b649a_8d93_5e65_aca3_12f0e94dd601); + impl windows_core::RuntimeType for IFoo { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + windows_core::imp::interface_hierarchy!( + IFoo, + windows_core::IUnknown, + windows_core::IInspectable + ); + impl windows_core::RuntimeName for IFoo { + const NAME: &'static str = "Test.IFoo"; + } + pub trait IFoo_Impl: windows_core::IUnknownImpl {} + impl IFoo_Vtbl { + pub const fn new() -> Self { + Self { + base__: windows_core::IInspectable_Vtbl::new::(), + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::IID + } + } + #[repr(C)] + #[doc(hidden)] + pub struct IFoo_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + } + windows_core::imp::define_interface!( + IFooFactory, + IFooFactory_Vtbl, + 0xcf8e99c0_2712_5467_ac7d_89f9097933fd + ); + impl windows_core::RuntimeType for IFooFactory { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + windows_core::imp::interface_hierarchy!( + IFooFactory, + windows_core::IUnknown, + windows_core::IInspectable + ); + impl IFooFactory { + pub fn CreateInstance( + &self, + name: i32, + outer: P1, + inner: &mut Option, + ) -> windows_result::Result + where + P1: windows_core::Param, + { + unsafe { + windows_core::imp::call_in_out(self, |this__, result__| { + (windows_core::Interface::vtable(self).CreateInstance)( + this__, + name, + outer.param().abi(), + inner as *mut _ as _, + result__, + ) + }) + } + } + } + impl windows_core::RuntimeName for IFooFactory { + const NAME: &'static str = "Test.IFooFactory"; + } + pub trait IFooFactory_Impl: windows_core::IUnknownImpl { + fn CreateInstance( + &self, + name: i32, + outer: windows_core::Ref, + inner: windows_core::OutRef, + ) -> windows_result::Result; + } + impl IFooFactory_Vtbl { + pub const fn new() -> Self { + unsafe extern "system" fn CreateInstance< + Identity: IFooFactory_Impl, + const OFFSET: isize, + >( + this: *mut core::ffi::c_void, + name: i32, + outer: *mut core::ffi::c_void, + inner: *mut *mut core::ffi::c_void, + result__: *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match IFooFactory_Impl::CreateInstance( + this, + name, + core::mem::transmute_copy(&outer), + core::mem::transmute_copy(&inner), + ) { + Ok(ok__) => { + result__.write(core::mem::transmute_copy(&ok__)); + core::mem::forget(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + Self { + base__: windows_core::IInspectable_Vtbl::new::(), + CreateInstance: CreateInstance::, + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::IID + } + } + #[repr(C)] + #[doc(hidden)] + pub struct IFooFactory_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub CreateInstance: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_result::HRESULT, + } + windows_core::imp::define_interface!( + IFooStatics, + IFooStatics_Vtbl, + 0x55a42e35_71fe_548e_9d28_6573a98f27c8 + ); + impl windows_core::RuntimeType for IFooStatics { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + windows_core::imp::interface_hierarchy!( + IFooStatics, + windows_core::IUnknown, + windows_core::IInspectable + ); + impl IFooStatics { + pub fn Reset(&self) -> windows_result::Result<()> { + unsafe { + windows_core::imp::call_in(self, |this__| { + (windows_core::Interface::vtable(self).Reset)(this__) + }) + } + } + pub fn Current(&self) -> windows_result::Result { + unsafe { + windows_core::imp::call_in_out(self, |this__, result__| { + (windows_core::Interface::vtable(self).Current)(this__, result__) + }) + } + } + } + impl windows_core::RuntimeName for IFooStatics { + const NAME: &'static str = "Test.IFooStatics"; + } + pub trait IFooStatics_Impl: windows_core::IUnknownImpl { + fn Reset(&self) -> windows_result::Result<()>; + fn Current(&self) -> windows_result::Result; + } + impl IFooStatics_Vtbl { + pub const fn new() -> Self { + unsafe extern "system" fn Reset( + this: *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + IFooStatics_Impl::Reset(this).into() + } + } + unsafe extern "system" fn Current( + this: *mut core::ffi::c_void, + result__: *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match IFooStatics_Impl::Current(this) { + Ok(ok__) => { + result__.write(core::mem::transmute_copy(&ok__)); + core::mem::forget(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + Self { + base__: windows_core::IInspectable_Vtbl::new::(), + Reset: Reset::, + Current: Current::, + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::IID + } + } + #[repr(C)] + #[doc(hidden)] + pub struct IFooStatics_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub Reset: unsafe extern "system" fn(*mut core::ffi::c_void) -> windows_result::HRESULT, + pub Current: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_result::HRESULT, + } + windows_core::imp::define_interface!(ITest, ITest_Vtbl, 0x28337665_44f1_5895_b1c3_726675c39971); + impl windows_core::RuntimeType for ITest { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + windows_core::imp::interface_hierarchy!( + ITest, + windows_core::IUnknown, + windows_core::IInspectable + ); + impl ITest { + pub fn DoSomething(&self) -> windows_result::Result<()> { + unsafe { + windows_core::imp::call_in(self, |this__| { + (windows_core::Interface::vtable(self).DoSomething)(this__) + }) + } + } + pub fn GetValue(&self) -> windows_result::Result { + unsafe { + windows_core::imp::call_in_out(self, |this__, result__| { + (windows_core::Interface::vtable(self).GetValue)(this__, result__) + }) + } + } + pub fn GetThing(&self) -> windows_result::Result { + unsafe { + windows_core::imp::call_in_out(self, |this__, result__| { + (windows_core::Interface::vtable(self).GetThing)(this__, result__) + }) + } + } + } + impl windows_core::RuntimeName for ITest { + const NAME: &'static str = "Test.ITest"; + } + pub trait ITest_Impl: windows_core::IUnknownImpl { + fn DoSomething(&self) -> windows_result::Result<()>; + fn GetValue(&self) -> windows_result::Result; + fn GetThing(&self) -> windows_result::Result; + } + impl ITest_Vtbl { + pub const fn new() -> Self { + unsafe extern "system" fn DoSomething( + this: *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + ITest_Impl::DoSomething(this).into() + } + } + unsafe extern "system" fn GetValue( + this: *mut core::ffi::c_void, + result__: *mut i32, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match ITest_Impl::GetValue(this) { + Ok(ok__) => { + result__.write(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + unsafe extern "system" fn GetThing( + this: *mut core::ffi::c_void, + result__: *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match ITest_Impl::GetThing(this) { + Ok(ok__) => { + result__.write(core::mem::transmute_copy(&ok__)); + core::mem::forget(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + Self { + base__: windows_core::IInspectable_Vtbl::new::(), + DoSomething: DoSomething::, + GetValue: GetValue::, + GetThing: GetThing::, + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::IID + } + } + #[repr(C)] + #[doc(hidden)] + pub struct ITest_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub DoSomething: + unsafe extern "system" fn(*mut core::ffi::c_void) -> windows_result::HRESULT, + pub GetValue: + unsafe extern "system" fn(*mut core::ffi::c_void, *mut i32) -> windows_result::HRESULT, + pub GetThing: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_result::HRESULT, + } +} diff --git a/crates/tests/fixtures/harness/data/bindgen/middleware/fixture.toml b/crates/tests/fixtures/harness/data/bindgen/middleware/fixture.toml new file mode 100644 index 0000000000..ca67429e76 --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/middleware/fixture.toml @@ -0,0 +1,6 @@ +filter = "Test" +no_allow = true +no_comment = true +middleware = true +specific_deps = true +references = ["../../../libs/bindgen/default"] diff --git a/crates/tests/fixtures/harness/data/bindgen/middleware/input.rdl b/crates/tests/fixtures/harness/data/bindgen/middleware/input.rdl new file mode 100644 index 0000000000..683ac8793b --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/middleware/input.rdl @@ -0,0 +1,36 @@ +// Exercises `--middleware` mode helper emission for the four call-site shapes: +// +// * `Default` arm — instance methods on `&self` (covered by `call_in` / `call_in_out`). +// * `Static` arm — class statics dispatched through `Self::IFoo(|this| { … })` +// (also covered by `call_in` / `call_in_out`, with `this` receiver). +// * `Composable` primary entry — non-aggregating constructor (still goes through +// `call_in_out`; the two outer/inner null placeholders are spliced +// into the helper's argument tail). +// * `Composable` `_compose` aggregating entry — covered by `call_compose`, which +// runs the `Compose::compose` / vtable / `let _ = &derived__;` / +// `from_abi` sequence in one place. +// +// The signatures of the generated `pub fn`s must remain byte-identical relative to the +// default emitter; only the body shape collapses. +#[winrt] +mod Test { + interface ITest { + fn DoSomething(&self); + fn GetValue(&self) -> i32; + fn GetThing(&self) -> ITest; + } + + #[Windows::Foundation::Metadata::Static(IFooStatics, 1)] + #[Windows::Foundation::Metadata::Composable(IFooFactory, Public, 1)] + class Foo { + IFoo, + } + interface IFoo {} + interface IFooStatics { + fn Reset(&self); + fn Current(&self) -> Foo; + } + interface IFooFactory { + fn CreateInstance(&self, name: i32, outer: Object, inner: &mut Object) -> Foo; + } +} diff --git a/crates/tests/fixtures/harness/tests/fixtures.rs b/crates/tests/fixtures/harness/tests/fixtures.rs index 084b6ef81f..b96aa50d91 100644 --- a/crates/tests/fixtures/harness/tests/fixtures.rs +++ b/crates/tests/fixtures/harness/tests/fixtures.rs @@ -102,6 +102,7 @@ struct FixtureConfig { no_allow: bool, no_comment: bool, noexcept: bool, + middleware: bool, specific_deps: bool, implement: bool, implements: Vec, @@ -156,6 +157,7 @@ impl FixtureConfig { "no_allow" => cfg.no_allow = parse_bool(value), "no_comment" => cfg.no_comment = parse_bool(value), "noexcept" => cfg.noexcept = parse_bool(value), + "middleware" => cfg.middleware = parse_bool(value), "specific_deps" => cfg.specific_deps = parse_bool(value), "implement" => cfg.implement = parse_bool(value), "implements" => cfg.implements = parse_string_list(value), @@ -378,6 +380,9 @@ fn run_bindgen(f: &Fixture) { if cfg.noexcept { bindgen.noexcept(); } + if cfg.middleware { + bindgen.middleware(); + } if cfg.implement { bindgen.implement(); }