mirror of
https://github.com/tonhowtf/omniget.git
synced 2026-10-02 07:54:34 +08:00
fix(windows): hold one MTA for the process instead of init/uninit per call
Opening the share picker twice crashed the app with an access violation. The capture paths wrapped each call in CoInitializeEx/CoUninitialize, but tearing the apartment down does not invalidate the WinRT activation factories the windows crate caches, so the second GraphicsCaptureSession::IsSupported read freed memory. CoIncrementMTAUsage exists for this: an implicit MTA that outlives every call, with the cookie deliberately never released. The worker threads stop initialising their own apartments for the same reason. Found by the capture self-test on the Windows CI job, which is why it exists.
This commit is contained in:
@@ -18,9 +18,7 @@ use windows::Win32::Media::Audio::{
|
||||
WAVEFORMATEX,
|
||||
};
|
||||
use windows::Win32::System::Com::StructuredStorage::{PROPVARIANT, PROPVARIANT_0_0};
|
||||
use windows::Win32::System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, BLOB, CLSCTX_ALL, COINIT_MULTITHREADED,
|
||||
};
|
||||
use windows::Win32::System::Com::{CoCreateInstance, CoIncrementMTAUsage, BLOB, CLSCTX_ALL};
|
||||
use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject};
|
||||
use windows::Win32::System::Variant::VT_BLOB;
|
||||
|
||||
@@ -332,14 +330,11 @@ pub fn start_process_loopback(
|
||||
let thread = std::thread::Builder::new()
|
||||
.name("omnidisc-wasapi".into())
|
||||
.spawn(move || {
|
||||
let com = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
|
||||
ensure_mta();
|
||||
let ready = match Event::new() {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(e));
|
||||
if com.is_ok() {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -354,9 +349,6 @@ pub fn start_process_loopback(
|
||||
let _ = tx.send(Err(e));
|
||||
}
|
||||
}
|
||||
if com.is_ok() {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
})
|
||||
.map_err(|e| StreamError::Capture(format!("wasapi thread: {e}")))?;
|
||||
match rx.recv() {
|
||||
@@ -441,19 +433,21 @@ pub fn audio_apps() -> Vec<AudioApp> {
|
||||
|
||||
/// COM apartment guard for the enumeration helpers, which may run on a tokio
|
||||
/// blocking thread whose apartment we do not control.
|
||||
pub struct ComGuard(bool);
|
||||
|
||||
impl ComGuard {
|
||||
pub fn mta() -> Self {
|
||||
let hr = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
|
||||
Self(hr.is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ComGuard {
|
||||
fn drop(&mut self) {
|
||||
if self.0 {
|
||||
unsafe { CoUninitialize() };
|
||||
/// Guarantees the process has a multithreaded apartment, once, for good.
|
||||
///
|
||||
/// The obvious shape — initialise COM on entry, uninitialise on the way out —
|
||||
/// crashes. `CoUninitialize` tears the apartment down, but the activation
|
||||
/// factories the windows crate caches for WinRT types survive it, so the next
|
||||
/// `GraphicsCaptureSession::IsSupported` dereferences freed memory: opening the
|
||||
/// share picker a second time was enough to fault. `CoIncrementMTAUsage` is the
|
||||
/// API for this exact need — it keeps an implicit MTA alive for every thread
|
||||
/// that never initialises one itself, and the cookie is deliberately never
|
||||
/// released.
|
||||
pub fn ensure_mta() {
|
||||
static MTA: std::sync::OnceLock<()> = std::sync::OnceLock::new();
|
||||
MTA.get_or_init(|| {
|
||||
if let Err(e) = unsafe { CoIncrementMTAUsage() } {
|
||||
tracing::warn!("[omnidisc-media] could not hold an MTA open: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ pub struct Platform;
|
||||
|
||||
impl CaptureApi for Platform {
|
||||
fn list_sources(thumbnails: bool) -> Result<StreamSources, StreamError> {
|
||||
let _com = audio::ComGuard::mta();
|
||||
audio::ensure_mta();
|
||||
sources::trace("com guard ready");
|
||||
wgc_available()?;
|
||||
sources::trace("wgc available");
|
||||
@@ -209,7 +209,7 @@ impl CaptureApi for Platform {
|
||||
// `GraphicsCaptureSession::IsSupported` is a WinRT static call, so the
|
||||
// caller's thread needs an apartment even though the session itself
|
||||
// runs on our own.
|
||||
let _com = audio::ComGuard::mta();
|
||||
audio::ensure_mta();
|
||||
wgc_available()?;
|
||||
let gpu = gpu()?;
|
||||
let target = match opts.source {
|
||||
|
||||
@@ -17,7 +17,6 @@ use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::Graphics::Direct3D11::{ID3D11Texture2D, D3D11_BOX};
|
||||
use windows::Win32::Graphics::Dxgi::IDXGIDevice;
|
||||
use windows::Win32::Graphics::Gdi::HMONITOR;
|
||||
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_MULTITHREADED};
|
||||
use windows::Win32::System::WinRT::Direct3D11::{
|
||||
CreateDirect3D11DeviceFromDXGIDevice, IDirect3DDxgiInterfaceAccess,
|
||||
};
|
||||
@@ -258,7 +257,7 @@ pub fn start(
|
||||
let thread = std::thread::Builder::new()
|
||||
.name("omnidisc-wgc".into())
|
||||
.spawn(move || {
|
||||
let com = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
|
||||
super::audio::ensure_mta();
|
||||
let last_frame_us = Arc::new(AtomicU64::new(0));
|
||||
let started = capture_item(&target).and_then(|item| {
|
||||
let size: SizeInt32 = item.Size().map_err(|e| cap_err("item size", e))?;
|
||||
@@ -283,9 +282,6 @@ pub fn start(
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(e));
|
||||
if com.is_ok() {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -299,9 +295,6 @@ pub fn start(
|
||||
}
|
||||
let _ = session.session.Close();
|
||||
let _ = session.pool.Close();
|
||||
if com.is_ok() {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
})
|
||||
.map_err(|e| StreamError::Capture(format!("wgc thread: {e}")))?;
|
||||
match rx.recv() {
|
||||
|
||||
Reference in New Issue
Block a user