Skip to content

Commit

Permalink
wasapi: Allow both threading models and switch the default to STA
Browse files Browse the repository at this point in the history
COM can prevent undefined behavior in either concurrency model by
performing marshaling when necessary. As a result, CoInitializeEx can be
called with either concurrency model, and in this case STA provides better
compatibility with other code requiring STA, e.g. ASIO backend or winit
drag-and-drop.

To dive into the detail, the entry point of WASAPI, MMDeviceEnumerator, is
registered with "both" threading model, which means that COM objects are
created with whatever the thread's concurrency model is set to. This raises
the concern that when STA is used, marshaling might make audio buffer
operations block on the main thread, breaking continuous audio processing.
However, the implementation actually uses free-threaded marshaller for
interfaces dealing with buffer operations, which effectively bypasses COM's
compatibility marshaling behavior and perform any API calls on the caller's
thread instead. Therefore, the interfaces would operate just fine on either
concurrency model.

For more details on COM's threading model, see [1].

[1] https://thrysoee.dk/InsideCOM+/ch04d.htm

Co-Authored-By: Henrik Rydgård <hrydgard@gmail.com>

Close RustAudio#59
Close RustAudio#348
Close RustAudio#504
Close RustAudio#530
Close RustAudio#538
Close RustAudio#572
  • Loading branch information
ishitatsuyuki committed Aug 8, 2021
1 parent 04c93f4 commit 03947af
Showing 1 changed file with 29 additions and 8 deletions.
37 changes: 29 additions & 8 deletions src/host/wasapi/com.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
//! Handles COM initialization and cleanup.
use super::check_result;
use super::IoError;
use std::marker::PhantomData;
use std::ptr;

use super::winapi::shared::winerror::{HRESULT, RPC_E_CHANGED_MODE, SUCCEEDED};
use super::winapi::um::combaseapi::{CoInitializeEx, CoUninitialize};
use super::winapi::um::objbase::COINIT_MULTITHREADED;
use super::winapi::um::objbase::COINIT_APARTMENTTHREADED;

thread_local!(static COM_INITIALIZED: ComInitialized = {
unsafe {
// this call can fail if another library initialized COM in single-threaded mode
// handling this situation properly would make the API more annoying, so we just don't care
check_result(CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED)).unwrap();
ComInitialized(ptr::null_mut())
// Try to initialize COM with STA by default to avoid compatibility issues with the ASIO
// backend (where CoInitialize() is called by the ASIO SDK) or winit (where drag and drop
// requires STA).
// This call can fail with RPC_E_CHANGED_MODE if another library initialized COM with MTA.
// That's OK though since COM ensures thread-safety/compatibility through marshalling when
// necessary.
let result = CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED);
if SUCCEEDED(result) || result == RPC_E_CHANGED_MODE {
ComInitialized {
result,
_ptr: PhantomData,
}
} else {
// COM initialization failed in another way, something is really wrong.
panic!("Failed to initialize COM: {}", IoError::from_raw_os_error(result));
}
}
});

/// RAII object that guards the fact that COM is initialized.
///
// We store a raw pointer because it's the only way at the moment to remove `Send`/`Sync` from the
// object.
struct ComInitialized(*mut ());
struct ComInitialized {
result: HRESULT,
_ptr: PhantomData<*mut ()>,
}

impl Drop for ComInitialized {
#[inline]
fn drop(&mut self) {
unsafe { CoUninitialize() };
// Need to avoid calling CoUninitialize() if CoInitializeEx failed since it may have
// returned RPC_E_MODE_CHANGED - which is OK, see above.
if SUCCEEDED(self.result) {
unsafe { CoUninitialize() };
}
}
}

Expand Down

0 comments on commit 03947af

Please sign in to comment.