Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Windows main thread checks more forgiving #4003

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 43 additions & 7 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,22 @@ impl EventLoop {
) -> Result<Self, EventLoopError> {
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 {
Expand Down Expand Up @@ -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:
/// <https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-160>
#[cfg(debug_assertions)]
fn main_thread_id() -> u32 {
static mut MAIN_THREAD_ID: u32 = 0;

Expand All @@ -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<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
Expand Down