tympan-apo 0.1.0

Rust framework for Windows Audio Processing Objects (APOs)
Documentation
//! User-facing macros.
//!
//! The only macro for now is [`crate::register_apo`], which emits the
//! four standard COM in-process server entry points
//! (`DllGetClassObject`, `DllCanUnloadNow`, `DllRegisterServer`,
//! `DllUnregisterServer`) wired to a `T: ProcessingObject`.

/// Register a `T: ProcessingObject` as the APO this DLL exposes.
///
/// Emits, in the calling scope:
///
/// - A `static __TYMPAN_APO_VTABLE` of type
///   [`crate::raw::class_factory::ApoVTable`] populated from
///   `T`'s associated constants and a creator function that
///   yields `Arc<dyn AnyApoInstance>` over [`crate::instance::ApoInstance<T>`].
/// - A `static __TYMPAN_APO_REGISTRY` array of length 1
///   pointing at the vtable.
/// - `#[no_mangle] pub unsafe extern "system" fn DllGetClassObject`
///   that forwards to
///   [`crate::raw::exports::dll_get_class_object_dispatch`] with
///   the registry above.
/// - `#[no_mangle] pub unsafe extern "system" fn DllCanUnloadNow`,
///   `DllRegisterServer`, and `DllUnregisterServer` as stubs
///   (return `S_FALSE` / `S_OK` / `S_OK` respectively; real
///   registry-write bodies land in a later PR).
///
/// ## Usage
///
/// ```ignore
/// use tympan_apo::{ProcessingObject, ProcessInput, BufferFlags,
///                  RealtimeContext, ApoCategory, Clsid};
///
/// struct MyApo;
/// impl ProcessingObject for MyApo {
///     const CLSID: Clsid = Clsid::from_u128(
///         0x12345678_1234_5678_1234_567812345678);
///     const NAME: &'static str = "My APO";
///     const COPYRIGHT: &'static str = "© 2026";
///     const CATEGORY: ApoCategory = ApoCategory::Sfx;
///     fn new() -> Self { Self }
///     fn process(
///         &mut self,
///         _rt: &RealtimeContext,
///         input: ProcessInput<'_>,
///         output: &mut [f32],
///     ) -> BufferFlags {
///         output.copy_from_slice(input.samples());
///         input.flags()
///     }
/// }
///
/// tympan_apo::register_apo!(MyApo);
/// ```
///
/// ## Single call per crate
///
/// The macro emits items with fixed `__TYMPAN_APO_*` symbol
/// names, including `#[no_mangle]` entry points which must be
/// unique in the link tree. Each cdylib must therefore invoke
/// `register_apo!` exactly once at the crate root.
#[macro_export]
macro_rules! register_apo {
    ($t:ty) => {
        /// Creator routed into the `ApoVTable::create` function
        /// pointer slot.
        #[doc(hidden)]
        fn __tympan_apo_create() -> ::std::sync::Arc<dyn $crate::instance::AnyApoInstance> {
            ::std::sync::Arc::new($crate::instance::ApoInstance::<$t>::new())
        }

        /// Per-APO vtable, consumed by `dll_get_class_object_dispatch`.
        #[doc(hidden)]
        pub static __TYMPAN_APO_VTABLE: $crate::raw::class_factory::ApoVTable =
            $crate::raw::class_factory::ApoVTable {
                clsid: <$t as $crate::ProcessingObject>::CLSID,
                name: <$t as $crate::ProcessingObject>::NAME,
                copyright: <$t as $crate::ProcessingObject>::COPYRIGHT,
                category: <$t as $crate::ProcessingObject>::CATEGORY,
                create: __tympan_apo_create,
            };

        /// Registry handed to `dll_get_class_object_dispatch`.
        #[doc(hidden)]
        static __TYMPAN_APO_REGISTRY: [&'static $crate::raw::class_factory::ApoVTable; 1] =
            [&__TYMPAN_APO_VTABLE];

        /// COM class-object factory entry point.
        ///
        /// # Safety
        ///
        /// Called by COM; arguments follow the
        /// `DllGetClassObject` ABI documented at
        /// <https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-dllgetclassobject>.
        #[no_mangle]
        pub unsafe extern "system" fn DllGetClassObject(
            rclsid: *const $crate::GUID,
            riid: *const $crate::GUID,
            ppv: *mut *mut ::core::ffi::c_void,
        ) -> $crate::HRESULT {
            // Safety: forwarded directly from the COM caller.
            unsafe {
                $crate::raw::exports::dll_get_class_object_dispatch(
                    rclsid,
                    riid,
                    ppv,
                    &__TYMPAN_APO_REGISTRY,
                )
            }
        }

        /// COM unload-readiness query.
        ///
        /// Returns `S_OK` (0) when no `ApoInstanceCom` is
        /// outstanding (the COM loader may unload the DLL),
        /// `S_FALSE` (1) otherwise. The counter is maintained in
        /// [`tympan_apo::raw::exports`] via per-instance inc/dec
        /// in `ApoInstanceCom::new` / `Drop`.
        ///
        /// # Safety
        ///
        /// Called by COM; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllCanUnloadNow() -> $crate::HRESULT {
            $crate::raw::exports::dll_can_unload_now_dispatch()
        }

        /// COM self-registration entry point.
        ///
        /// Writes the per-user `HKEY_CURRENT_USER\Software\Classes\CLSID\{<clsid>}`
        /// subtree expected by the COM activator; see
        /// [`tympan_apo::raw::register`] for the value layout.
        /// Pair with `regsvr32 /n /i:user <dll>` — `/n` skips the
        /// default machine-wide registration that requires admin.
        ///
        /// # Safety
        ///
        /// Called by `regsvr32`; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllRegisterServer() -> $crate::HRESULT {
            $crate::raw::exports::dll_register_server_dispatch(&__TYMPAN_APO_REGISTRY)
        }

        /// Inverse of `DllRegisterServer`. Idempotent — missing
        /// keys are not an error.
        ///
        /// # Safety
        ///
        /// Called by `regsvr32 /u`; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllUnregisterServer() -> $crate::HRESULT {
            $crate::raw::exports::dll_unregister_server_dispatch(&__TYMPAN_APO_REGISTRY)
        }
    };
}

/// Register a `T: AecProcessingObject` as the AEC APO this DLL
/// exposes. Mirrors `register_apo!` but uses the AEC carrier
/// ([`AecApoInstanceCom`](crate::aec::instance_com::AecApoInstanceCom))
/// and the nine-IID `APO_REG_PROPERTIES` interface list.
///
/// Emits, in the calling scope:
///
/// - `static __TYMPAN_AEC_APO_VTABLE: AecApoVTable` populated from
///   `T`'s associated constants and an AEC creator that yields
///   `Arc<dyn AnyAecApoInstance>` over
///   `crate::aec::AecApoInstance<T>`.
/// - `static __TYMPAN_AEC_APO_REGISTRY: [&'static AecApoVTable; 1]`
///   pointing at the vtable above.
/// - The four standard `#[no_mangle] pub unsafe extern "system"`
///   `Dll*` entry points wired to the AEC dispatch helpers in
///   [`crate::aec::exports`].
///
/// ## Single call per crate
///
/// Same constraint as `register_apo!`: each cdylib invokes
/// `register_aec_apo!` exactly once. A given DLL is either an AEC
/// APO or a plain APO, never both.
#[cfg(feature = "aec")]
#[macro_export]
macro_rules! register_aec_apo {
    ($t:ty) => {
        /// Creator routed into the `AecApoVTable::create` slot.
        #[doc(hidden)]
        fn __tympan_aec_apo_create() -> ::std::sync::Arc<dyn $crate::aec::AnyAecApoInstance> {
            ::std::sync::Arc::new($crate::aec::AecApoInstance::<$t>::new())
        }

        /// Per-AEC-APO vtable.
        #[doc(hidden)]
        pub static __TYMPAN_AEC_APO_VTABLE: $crate::aec::class_factory::AecApoVTable =
            $crate::aec::class_factory::AecApoVTable {
                clsid: <$t as $crate::ProcessingObject>::CLSID,
                name: <$t as $crate::ProcessingObject>::NAME,
                copyright: <$t as $crate::ProcessingObject>::COPYRIGHT,
                category: <$t as $crate::ProcessingObject>::CATEGORY,
                create: __tympan_aec_apo_create,
            };

        /// Registry handed to the AEC dispatch helpers.
        #[doc(hidden)]
        static __TYMPAN_AEC_APO_REGISTRY: [&'static $crate::aec::class_factory::AecApoVTable; 1] =
            [&__TYMPAN_AEC_APO_VTABLE];

        /// COM class-object factory entry point.
        ///
        /// # Safety
        ///
        /// Called by COM; arguments follow the `DllGetClassObject`
        /// ABI.
        #[no_mangle]
        pub unsafe extern "system" fn DllGetClassObject(
            rclsid: *const $crate::GUID,
            riid: *const $crate::GUID,
            ppv: *mut *mut ::core::ffi::c_void,
        ) -> $crate::HRESULT {
            unsafe {
                $crate::aec::exports::aec_dll_get_class_object_dispatch(
                    rclsid,
                    riid,
                    ppv,
                    &__TYMPAN_AEC_APO_REGISTRY,
                )
            }
        }

        /// COM unload-readiness query.
        ///
        /// # Safety
        ///
        /// Called by COM; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllCanUnloadNow() -> $crate::HRESULT {
            $crate::raw::exports::dll_can_unload_now_dispatch()
        }

        /// COM self-registration entry point.
        ///
        /// # Safety
        ///
        /// Called by `regsvr32`; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllRegisterServer() -> $crate::HRESULT {
            $crate::aec::exports::aec_dll_register_server_dispatch(&__TYMPAN_AEC_APO_REGISTRY)
        }

        /// Inverse of `DllRegisterServer`. Idempotent — missing
        /// keys are not an error.
        ///
        /// # Safety
        ///
        /// Called by `regsvr32 /u`; takes no parameters.
        #[no_mangle]
        pub unsafe extern "system" fn DllUnregisterServer() -> $crate::HRESULT {
            $crate::aec::exports::aec_dll_unregister_server_dispatch(&__TYMPAN_AEC_APO_REGISTRY)
        }
    };
}