diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2b04d1a69d..c0d43fd3ec 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -158,6 +158,8 @@ changelog entry. - In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`. - Remove `Force::Calibrated::altitude_angle`. - On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`. +- Main thread check is now more lax on Windows due to it not being fully reliable: it now produces a log instead + of a hard panic, and is not checked without `cfg(debug_assertions)`. ### Removed diff --git a/src/event_loop.rs b/src/event_loop.rs index 059613459b..09dffbeaa4 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -71,7 +71,7 @@ impl EventLoopBuilder { /// /// ## Panics /// - /// Attempting to create the event loop off the main thread will panic. This + /// Attempting to create the event loop off the main thread may log or panic. This /// restriction isn't strictly necessary on all platforms, but is imposed to /// eliminate any nasty surprises when porting to platforms that require it. /// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 874ae06a8c..674bb8bb4e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -186,13 +186,22 @@ impl EventLoop { ) -> Result { let thread_id = unsafe { GetCurrentThreadId() }; + #[cfg(debug_assertions)] if !attributes.any_thread && thread_id != main_thread_id() { - panic!( - "Initializing the event loop outside of the main thread is a significant \ - cross-platform compatibility hazard. If you absolutely need to create an \ - EventLoop on a different thread, you can use the \ - `EventLoopBuilderExtWindows::with_any_thread` function." - ); + const MESSAGE: &str = "Initializing the event loop outside of the main thread is a \ + significant cross-platform compatibility hazard. If you \ + absolutely need to create an EventLoop on a different thread, \ + you can use the `EventLoopBuilderExtWindows::with_any_thread` \ + function."; + + // If we're not the main module, it's possible for main_thread_id() to be incorrect. + // See https://github.com/rust-windowing/winit/issues/3999 + // In that case, log a warning instead of an outright panic. + if is_self_main_module() { + panic!("{}", MESSAGE); + } else { + tracing::warn!("{}", MESSAGE); + } } if attributes.dpi_aware { @@ -556,10 +565,12 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle { /// to setup global state within a program. The OS will call a list of function pointers which /// assign values to a static variable. To have get a hold of the main thread id, we need to place /// our function pointer inside of the `.CRT$XCU` section so it is called before the main -/// entrypoint. +/// entrypoint. Note that when compiled into a dylib, this is not guaranteed to be ran from the +/// "main" thread, so this is not foolproof. /// /// Full details of CRT initialization can be found here: /// +#[cfg(debug_assertions)] fn main_thread_id() -> u32 { static mut MAIN_THREAD_ID: u32 = 0; @@ -583,6 +594,31 @@ fn main_thread_id() -> u32 { unsafe { MAIN_THREAD_ID } } +/// Check if winit is compiled into the main module (.exe). +#[cfg(debug_assertions)] +fn is_self_main_module() -> bool { + use std::ptr::null; + + use windows_sys::Win32::Foundation::HMODULE; + use windows_sys::Win32::System::LibraryLoader::{ + GetModuleHandleA, GetModuleHandleExA, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + }; + + unsafe { + let mut winit_module: HMODULE = 0; + GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + is_self_main_module as *const u8, + &mut winit_module, + ); + + let main_module = GetModuleHandleA(null()); + + winit_module == main_module + } +} + /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`)