0

I'm trying to write a simple windows app in rust with windows-rs crate. I'd like to have a state wrapped in Counter struct and pass it to window_proc function. It is successful to retrieve the pointer and store it by GWLP_USERDATA index. My question is that why it increases in WM_CREATE msg without error, but does not work in WM_COMMAND msg?

struct Counter {
    counter: i32
}

fn main() -> Result<()> {
    let lparam: *mut Counter = Box::leak(Box::new(Counter{
        counter: 1
    }));

    unsafe {
        let mut instance = Default::default();
        GetModuleHandleExW(0, None, &mut instance)?;

        let wnd_class = WNDCLASSEXW {
           // ... ...
        };

        let atom = RegisterClassExW(&wnd_class);
        debug_assert!(atom != 0);

        let hwnd = CreateWindowExW(
            // ... ...
            Some(lparam as *mut c_void),
        );

        // ... ...
    }
}

fn increase_and_print(parent: HWND) {
    unsafe {
        let ptr = GetWindowLongPtrW(parent, GWLP_USERDATA);
        println!("GetWindowLongPtrW: {}", ptr);

        let ptr = GetWindowLongPtrW(parent, GWLP_USERDATA) as *mut Box<Counter>;
        debug_assert!(!ptr.is_null());
        let c =  &mut *ptr;
        c.counter += 1;
        println!("counter: {}", c.counter);
    }
}

extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    unsafe {
        match msg {
            WM_CREATE => {

                // Create a click button
        
                // Retrieve the pointer to your state and store it in the window's user data
                SetWindowLongPtrW(hwnd, GWLP_USERDATA, lparam.0 as isize);
                increase_and_print(hwnd);

                LRESULT(0)
            },
            WM_DESTROY => {
                PostQuitMessage(0);
                LRESULT(0)
            },
            WM_COMMAND => {
                increase_and_print(hwnd);
                return LRESULT(0);
            },
            _ => DefWindowProcW(hwnd, msg, wparam, lparam)
        }
    }
}

The counter is initialized to 1, then increased to 2 in WM_CREATE msg. I expect it prints 3 after clicking the button (i.e. WM_COMMAND msg was called) Output in console

GetWindowLongPtrW: 359815702944
counter: 2
GetWindowLongPtrW: 359815702944
thread 'main' panicked at 'misaligned pointer dereference: address must be a multiple of 0x4 but is 0xb', apps\counter\src\main.rs:73:9

Complete code

10
  • Why do you call GetWindowLongPtrW twice? The second one could be simply let ptr = ptr as *mut Box<Counter>; (that's probably not what causes the problem, but it would make sure that GetWindowLongPtrW doesn't return a different value the second time…) Commented Jan 1, 2024 at 9:18
  • On second thought, why do you even have a Box at this point? It should be let ptr = ptr as *mut Counter; since calling Box::leak removed the Box and left a bare reference. Commented Jan 1, 2024 at 9:22
  • There's literally nothing simple about writing a Windows application in Rust, regardless of how little you intend this application to be doing. The only code I've seen so far that gets this mostly right is Raph Levien's win-win implementation. The comments are particularly insightful. Commented Jan 1, 2024 at 9:42
  • Thanks @Jmb, as you said it just make sure that GetWindowLongPtrW always returns a same pointer. The Box is required here, if I remove the Box in ` GetWindowLongPtrW(parent, GWLP_USERDATA) as *mut Box<Counter>;`, I cloud not get the initial value 1. Commented Jan 1, 2024 at 14:35
  • If you need the Box to get the initial value 1, then that means you probably really need let ptr = *(ptr as *const *mut Counter); (I don't know GetWindowLongPtrW, or really any of the Windows API, but casting to a Box here is just plain wrong since the value you passed to CreateWindowExW is not a Box, so there's no reason for Windows to give you one back). Commented Jan 1, 2024 at 16:21

1 Answer 1

0

I search for apps created by windows-rs on GitHub, finally I got this robmikh/minesweeper-rs windows.rs. It's totally what I was looking for.

Set counter to User data

let cs = lparam.0 as *const CREATESTRUCTW;
let counter = (*cs).lpCreateParams as *mut Counter;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, counter as _);

Retrieve counter and update its value

unsafe {
    let counter_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut Counter;
    if let Some(c) = counter_ptr.as_mut() {
        c.counter += 1;
        println!("counter: {}", c.counter);
    }
}

As I am new to Rust, I would be grateful if somebody could explain this in detail.

Sign up to request clarification or add additional context in comments.

1 Comment

When you register a window class, the system allocates additional memory for every HWND created. Get|SetWindowLongPtr accesses that extra memory by index. The GWLP_USERDATA constant refers to a pointer-sized memory location that is being used to store a pointer to Counter. as_mut() performs a null-pointer check and returns Some(&'a T) for a non-null pointer to T.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.