It's been there since at least 2005, so it's sure been a while 
Be careful here -- depending on what definition of HWND
you're using, your Window
struct may be Send
/Sync
, so you may inadvertently be exposing the Rc
cross-thread, which is unsound. I'm also not completely confident that the winproc is 100% guaranteed to only be called on the same thread it's initialized from; I think it is, but I'd need to double-check the documentation before I'm fully comfortable relying on that to be true. If my memory serves me, giving a NULL
HWND
to the message pumping API only processes/dispatches messages owned on the current thread, so it's probably fully sound to say that if you don't expose Send
/Sync
access to the HWND
that messages will only be dispatched on the single thread with access to the HWND
. It's probably even an error to use a window HWND
from a thread other than the creating one; I just barely know enough to know what to be wary of.
(winapi's HWND
is not Send
/Sync
, but the windows crate's is, so if you're using the windows crate you probably want to put a PhantomData<*const Window>
in your Window
, representing by-example the *const Window
stored in the GWLP_USERDATA
region.)
I'm assuming you've written something with the shape of fn new() -> Rc<Window>
. There's now a three-way choice to be made: do you store Rc::as_ptr
, Rc::into_raw
, or rc::Weak::into_raw
on the window? Rc::into_raw
is sound but wrong, as now it's impossible for the Window
to drop, since it's holding a strong reference to itself. Storing Rc::as_ptr
is unsound; Rc::get_mut
allows you to get &mut Window
access and cause problems with that. You can make it sound, but you'd need to make Window
not Unpin
and use Pin<Rc<Window>>
instead. Storing Weak::into_raw
is thus the safest option, so long as you upgrade the weak reference from the window procedure instead of just assuming the pointer is valid; Rc::make_mut
exists and will invalidate your weak references. Rc::new_cyclic
exists specifically to enable this Weak<Self>
pattern in pure safe Rust code.
There's actually also one other alternative: if your Window
is only the HWND
and no other state, you don't actually need to put it behind another allocation. You can instead just create a temporary imitation Window
and call the method on that, e.g.
let my_window = ManuallyDrop::new(Window { inner: hwnd });
my_window.window_proc()
and "all" you have to do to ensure this is okay is make sure the window procedure never has the opportunity to drop your imitation Window
(thus ManuallyDrop
) and never assume &mut Window
means exclusive access to the HWND
.
... though all of these are implicitly assuming that the window procedure is only called for messages for instances of Window
, and you don't get other bonus messages. Cross-referencing my C++ WinProc I'm using (which does use a global), I'd probably write this as roughly
struct Window {
hwnd: HWND,
// other state/fields
marker: PhantomData<Weak<Self>>,
}
impl Window {
pub fn new() -> Rc<Self> {
let hwnd = /* ... */;
Rc::new_cyclic(|weak| {
SetWindowLongPtrW(hwnd, GWLP_USERDATA, weak.into_raw() as LONG_PTR);
Window { hwnd, ..., marker: PhantomData }
})
}
fn proc(&self, msg: UINT, w: WPARAM, l: LPARAM) -> LRESULT {
// ...
}
}
unsafe extern "system" fn win_msg_proc(hwnd: HWND, msg: UINT, w: WPARAM, l: LPARAM) -> LRESULT {
if hwnd.is_null() {
return DefWindowProcW(hwnd, msg, w, l); // bonus thread message, probably
}
let raw = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *const Window;
if raw.is_null() {
return DefWindowProcW(hwnd, msg, w, l); // weird, different window source?
}
let weak = ManuallyDrop::new(Weak::from_raw(raw));
let Some(window) = weak.upgrade()
else {
return DefWindowProcW(hwnd, msg, w, l); // lost window
};
window.proc(msg, w, l)
}
(completely untested)