Search code examples
windowsrustwindowhwndcrate

Rust Windows Crate CreateWindowExW


I am writing a function to create a window and I have provide the code which isn't working below. This code is using the windows crate and the latest version of rust (at time of writing rustc 1.74.0.

use std::ptr;
 
use windows::{
    core::{w, PCWSTR},
    Win32::{
        Foundation::{GetLastError, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
        Graphics::Gdi::HBRUSH,
        System::LibraryLoader::GetModuleHandleW,
        UI::WindowsAndMessaging::{
            CreateWindowExW, DefWindowProcW, DestroyWindow, PostQuitMessage, RegisterClassW,
            CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, HCURSOR, HICON, WINDOW_EX_STYLE, WNDCLASSW,
            WS_OVERLAPPEDWINDOW,
        },
    },
};
 
fn main() {
    match create_window() {
        Ok(_) => {}
        Err(e) => eprintln!("{}", e),
    };
}
 
pub fn create_window() -> Result<(), Box<dyn std::error::Error>> {
    let h_instance: HINSTANCE = unsafe { GetModuleHandleW(None) }?.into();
    let lp_sz_class_name = w!("window_class");
    let lp_sz_window_name = w!("window_title");
 
    let window_class = WNDCLASSW {
        style: CS_HREDRAW | CS_VREDRAW,
        lpfnWndProc: Some(window_proc),
        cbClsExtra: 0,
        cbWndExtra: 0,
        hInstance: h_instance,
        hIcon: HICON(0),
        hCursor: HCURSOR(0),
        hbrBackground: HBRUSH(2),
        lpszMenuName: PCWSTR(ptr::null_mut()),
        lpszClassName: lp_sz_class_name,
    };
 
    if unsafe { RegisterClassW(&window_class) } == 0 {
        unsafe { GetLastError() }?;
    };
 
    let hwnd = unsafe {
        CreateWindowExW(
            WINDOW_EX_STYLE(0),
            window_class.lpszClassName,
            lp_sz_window_name,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            None,
            None,
            window_class.hInstance,
            None,
        )
    };
 
    if hwnd == HWND(0) {
        unsafe { GetLastError() }?;
    }
 
    Ok(())
}
 
unsafe extern "system" fn window_proc(
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    match msg {
        WM_CLOSE => {
            unsafe { PostQuitMessage(0) };
            unsafe { DestroyWindow(hwnd) };
            LRESULT(0)
        }
        WM_DESTROY => {
            unsafe { PostQuitMessage(0) };
            LRESULT(0)
        }
        _ => return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
    }
}

When this code is running it produces this as an error "failed to create window > The parameter is incorrect. (0x80070057)". Indicating that a parameter to the function CreateWindowExW isn't valid.

I've attempted isolate the parameters 1 by 1 but all have data, and I am unable to locate the invalid parameter. I was wondering if anyone can take a look and see if I have missed anything?

I expected the window class to be registered which is successful, and the handle to the window to be created which is unsuccessful.


Solution

  • I compiled the MRE you pasted and it produces this warning that is a big red flag:

    warning: unreachable pattern
      --> src/main.rs:82:9
       |
    77 |         WM_CLOSE => {
       |         -------- matches any value
    ...
    82 |         WM_DESTROY => {
       |         ^^^^^^^^^^ unreachable pattern
       |
       = note: `#[warn(unreachable_patterns)]` on by default
    
    

    The relevant code is in window_proc is like this:

        match msg {
            WM_CLOSE => { /*... */ ; LRESULT(0) }
            WM_DESTROY => { ... }
            _ => DefWindowProcW(hwnd, msg, wparam, lparam),
        }
    

    But the constants WM_* are unknown to the Rust compiler so this is actually equivalent to this:

        match msg {
            _ => { /*... */ ; LRESULT(0) }
            /* blah, blah DefWindowProcW something that is never run */
        }
    

    That is, this function always returns LRESULT(0), no matter what message is processing. And as I commented before, one of the first messages a window function gets, even before CreateWindow returns, is WM_NCCREATE. But returning 0 for that message will abort the window creation. The value returned by GetLastError() will be whatever you set with SetLastError() but you didn't call that, so you get meaningless error code.

    The solution is easy, just import the message constants:

    unsafe extern "system" fn window_proc(
        hwnd: HWND,
        msg: u32,
        wparam: WPARAM,
        lparam: LPARAM,
    ) -> LRESULT {
        use windows::Win32::UI::WindowsAndMessaging::*; // <---- Here!
    
        match msg {
            WM_CLOSE => { ...
    

    That is why you should never ignore a Rust compiler warning. And why you should always add an MRE to a StackOverflow question!