Search code examples
oopwinapid

Member modified by method reverts after it exits


I have a window (Windows), and my wndProc is basically the same as the one on the windows guides. However, even though WM_CLOSE gets passed (and I can use if(msg == WM_CLOSE)), I can't seem to set my shouldClose flag. I've confirmed that I still get the event within my processMessage method. So my question is this: what is going on, and how can I make it work?

Edit: I tried storing the window data as a struct instead of a class, and everything works just fine. Ie. All I changed was the type of the class, and a few errors.

class Win32Window {
    this(wstring title, int width, int height) {
        immutable wstring className = "glass_def_class_name\0";
        auto hInstance = GetModuleHandle(null);

        WNDCLASSW wc;
        wc.lpfnWndProc = &windowProc;
        wc.hInstance = hInstance;
        wc.lpszClassName = &className[0];
        RegisterClassW(&wc);

        handle = CreateWindowExW(
            0,
            &className[0],
            &title[0],
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT,
            width, height,
            null, null,
            hInstance,
            cast(void*) this);

        ShowWindow(handle, SW_NORMAL);
    }

    ~this() {
        DestroyWindow(handle);
    }

    void processEvents() {
        MSG msg;
        while (PeekMessage(&msg, handle, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    bool shouldClose;
    HWND handle;

private:
    LRESULT processMessage(UINT msg, WPARAM wp, LPARAM lp) nothrow {
        switch (msg) {
        case WM_CLOSE:
            shouldClose = true;
            return 0;
        default:
            return DefWindowProc(handle, msg, wp, lp);
        }
    }
}

private extern (Windows) LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) nothrow {
    Win32Window window;

    if (msg == WM_CREATE) {
        CREATESTRUCT* create = cast(CREATESTRUCT*) lp;
        window = cast(Win32Window*) create.lpCreateParams;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR) create.lpCreateParams);
        window.handle = hwnd;
    }
    else {
        LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
        window = cast(Win32Window* ptr);
    }

    if (window)
        return window.processMessage(msg, wp, lp);
    else
        return DefWindowProc(hwnd, msg, wp, lp);
}

void main()
{
    auto window = new Win32Window("test", 1280, 720);
    while(window.shouldClose == false) {
        window.processEvents();
    }
    window.destroy();
}

Solution

  • It turns out that you can't actually cast a pointer directly to a reference. An intermediary is necessary (or something to that effect). Thus, the winProc should look something like this:

    private extern (Windows) LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) nothrow {
        Win32Window window;
    
        if (msg == WM_NCCREATE) {
            auto create = cast(CREATESTRUCT*) lp;
            window = cast(Win32Window) (cast(void*) create.lpCreateParams);
            window.handle = hwnd;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR) create.lpCreateParams);
        }
        else {
            window = cast(Win32Window) (cast(void*) GetWindowLongPtr(hwnd, GWLP_USERDATA));
        }
    
        return window ? window.processMessage(msg, wp, lp) : DefWindowProc(hwnd, msg, wp, lp);
    }
    

    Note the extra cast(void*) before create.lpCreateParams and GetWindowLongPtr(hwnd, GWLP_USERDATA). I'm not entirely sure why this works as opposed to my original code, but it seems to work, and I'll do some more investigating when I have the time.