raw-window-handle
raw-window-handle copied to clipboard
Should `RawWindowHandle` implement `Send`?
Windows are apparently effectively !Send
on Windows and macOS, and the lifetime issues that Android's window handles have make marking window handles Send
perhaps a bit suspect.
I believe we should treat RawWindowHandle
s somewhat like pointers, in that they can be constructed by safe code, but can't be used directly without unsafe
. Thus, we should make the handles Send
, but provide enough information in order for user code to tell if they're using the handle on the correct thread. Additionally, any API creating ready-made RawWindowHandle
s must be unsafe, as is already the case with HasRawWindowHandle
. This all assumes we can find a workable solution to #84.
I believe we should treat RawWindowHandles somewhat like pointers, in that they can be constructed by safe code, but can't be used directly without unsafe. Thus, we should make the handles Send, but provide enough information in order for user code to tell if they're using the handle on the correct thread.
Except pointers are !Send
And this isn't a super big deal because (unlike Copy) a type can always just force Send onto whatever data it wants.
I can't speak to macOS, and I don't particularly have a need for RawWindowHandle
to be Send
, but I want to chime in on the
windows side of things conceptually. I'm going to call a Windows Window a WND
, even though no such win32 type exists.
-
WND: !Send
, as you say. -
WND: Sync
, however, per Raymond Chen / The Old New Thing. -
HWND
~&WND
/Option<Weak<WND>>
, whereWND
has a ton of interior mutability:- Some Mutex-based (message queues?)
- Some atomic
- Some closer to UnsafeCell, caller beware.
-
&T: Send
ifT: Sync
- ergo it'd be sane for
HWND: Send
givenWND: Sync
(although winapi doesn't implementSend
for HWND currently.) - ergo it'd be sane for
RawWindowHandle: Send
, even thoughHasRawWindowHandle
likely can't be.
Methods like PostMessageW
etc. exist explicitly to interact with HWND
s belonging to another thread, and plenty of APIs exist to access HWNDs referencing other processes you don't control (GetDesktopWindow, GetForegroundWindow, GetShellWindow, GetConsoleWindow, ...).
This isn't to say windows don't have thread affinity - they do. A window is owned by a thread and cannot be transfered. But they can be referenced by other threads, and the HWND
is effecitvely a weak ptr/reference to said window.
You might wish to argue HWND
~ &mut WND
, given the mutable nature of the underlying window. I disagree: &mut
should be pronounced "exclusive reference", and there's nothing even remotely exclusive about a HWND
reference, and trying to deny that reality has caused me nothing but suffering and pain.
HWND
s are fundamentally unlockable, weak, data-racey, shared, and fragile, even in single threaded code, due to the incredibly recursion-prone nature of wndprocs. A not-uncommon bug in C++ codebases is for something to create a dialog (perhaps to open a file, perhaps to show an assertion message). These dialogs pump the message loop despite being in a wndproc. Any windows not specified as the parent of the dialog (of which there will always be at least 1 in a multi-window application) may be closed out from underneath their currently executing wndprocs or render loops! Any attempt to impose static lifetimes on general HWNDs is a fool's errand.
That leaves runtime enforcement of lifetimes. Fortunately there are many options:
- You can detect when a window is destroyed via message hooks, or by overriding an hwnd's wndproc with a wrapper, and:
- Destroy Direct3D devices etc. depending on said window before it's destroyed
- Invalidate any weak HWND references you might be holding onto
- Panic/bug/failfast/abort/hang before undefined behavior can occur if something still insists on "locking" the window.
- Window classes you author can defer destruction (to e.g. your main loop?) and merely hide the window when "closed".
- This won't prevent some random jerk from calling DestroyWindow on your window.
- This will prevent triggering any panic/bug/failfast/abort s in the regular execution of reasonable codebases.
- You can validate a given HWND is valid, and belongs to the current thread:
- Check
GetWindowThreadProcessId(hwnd, nullptr) == GetCurrentThreadId()
- Seems fine even with never-valid
hwnd
s in testing
- Check
- Much of Win32 already guards against invalid HWNDs without any need for extra checks by Rust code, returning ERROR_INVALID_WINDOW_HANDLE (or ERROR_ACCESS_DENIED for hwnds referencing other threads) and not crashing no matter how much I attempt to break the functions in question.
On Wayland the raw window handle is Send
+ Sync
for example.
However I do have a question on how it interacts with the graphics API usually? For example if I want to render a window on a different thread, I must pass wl_surface
to that thread and create EGLSurface
for it, what should be done here on e.g. Windows and macOS? Can I pass handle to simple create a EGLSurface
on windows from different thread? Since I'm pretty sure that you can't just send EGLSurface
(on Wayland you'll likely segfault later if you do that)?