I'm trying to use the WPARAM parameter in wndproc to get keyboard inputs using the "windows" crate. I found a github repo that tries to achieve the same thing (link to the specific code here) and they've managed to cast a WPARAM to an i32. When I try to do the exact same thing, I get the error Non-primitive cast: `WPARAM` as `i32` [E0605]
. Since the repo I found is pretty old I imagine there's been changes made to the crate and I'm not really sure what I should be doing differently. Here's a snippet of my code if it helps:
extern "system" fn wnd_proc(window: HWND, message: u32, w_param: WPARAM, l_param: LPARAM)
-> LRESULT {
unsafe {
match message {
...
WM_SYSKEYDOWN => {
let vk_code: i32 = w_param as i32;
}
...
}
}
}
TL;DR: Replace
w_param as i32
withw_param.0 as i32
.The
windows
crate makes liberal use of the new type idiom. This is a powerful tool leveraging the type system to guard against misinterpreting data of identical binary types. It does this by inventing wrapper types whose sole purpose is to capture and persist semantic information.This is incredibly useful in general. It opens the opportunity to turn what would have been a run-time error into a compile-time error. For example, you cannot pass the
HFONT
returned fromCreateFontW
intoCloseHandle
. WhileHFONT
s andHANDLE
s essentially are justisize
values at the binary interface, the new type wrappers prevent Rust code from confusing one for the other.This is how the
WPARAM
wrapper is defined in thewindows
crate:This is a tuple struct with a single field that can be accessed by index using the
.0
syntax. The effect of the#[repr(transparent)]
attribute is that theWPARAM
struct and its contained field have the same binary layout. This is crucial so thatWPARAM
can appear in an FFI in place of ausize
. After all, thewnd_proc
above is called by the system (written in C and C++) that doesn't (need to) know what a tuple struct is.All due praises aside, the new type idiom doesn't provide much value here. The
WPARAM
(usize
) andLPARAM
(isize
) values passed to the window procedure are essentially type-erased. Their meaning has to be recovered on a message-by-message basis, with frequent wrapping and unwrapping in between. To illustrate this, here is how aWM_SYSKEYDOWN
handler can be implemented:This may seem overly verbose, but that is in part due to re-encoding the (
u16
) value inside aVIRTUAL_KEY
wrapper. That can be trimmed down to a one-liner, e.g.,let vk = VIRTUAL_KEY(w_param.0 as _);
, leaving us with a sanity-checked new type that cannot accidentally get confused for an arbitraryu16
value.With this, we've come full circle: While the new type idiom doesn't provide much value to the
WPARAM
wrapper, it sure does once theusize
payload has been repacked into aVIRTUAL_KEY
struct, unleashing the new type idiom's full potential again.A note on why the code you've found on GitHub works without the added verbosity: It uses the
winapi
crate that models ABI types as type aliases. A type alias merely introduces a new name for an existing type.WPARAM
is thus an alternative spelling for theusize
type.This is very similar to what the
windows-sys
crate does: ItsWPARAM
type alias is identical to that of thewinapi
crate.Neither of these low-level binding crates makes much use of Rust's type system beyond the ABI guarantees.