1

I tried to create this window class which creates and shows a window. When I run this class, GetMessage keep on sending WM_PAINT message, and task manager shows me about 50% CPU usage just from my process.

main.cpp:

#include "Window.h"

int WINAPI WinMain(
    HINSTANCE /* hInstance */,
    HINSTANCE /* hPrevInstance */,
    LPSTR /* lpCmdLine */,
    int /* nCmdShow */
    )
{
    Window::GlobalInitialize();

    Window window(L"abcd", 500, 500);

    if (SUCCEEDED(window.Initialize()))
        window.RunMessageLoop();

    Window::GlobalTerminate();

    return 0;
}

Window.h:

#ifndef WINDOW_HEADER
#define WINDOW_HEADER

#include <Windows.h>

#include <functional>

#pragma comment(lib, "d2d1.lib")

class Window;

typedef std::function<void(Window *window, UINT id, LPCWSTR message)> ErrorCallback;

class Window {
public:
#define ERROR_FAILED_HINSTANCE 1
#define ERROR_FAILED_HINSTANCE_STR L"Failed to retrieve hInstance"

#define ERROR_FAILED_REGISTER 2
#define ERROR_FAILED_REGISTER_STR L"Failed to register window class"

#define ERROR_FAILED_CREATION 3
#define ERROR_FAILED_CREATION_STR L"Failed to create window"

    typedef std::function<HRESULT(Window *window)> WEOnCreate;
    typedef std::function<HRESULT(Window *window)> WEOnDestroy;
    typedef std::function<HRESULT(Window *window)> WEOnRender;
    typedef std::function<HRESULT(Window *window, UINT width, UINT height)> WEOnResize;
    typedef std::function<HRESULT(Window *window, UINT horizontalResolution, UINT verticalResolution)> WEOnScreenResolutionChange;

    Window(LPCWSTR title, UINT width, UINT height);
    ~Window();

    HRESULT SetSize(UINT width, UINT height);
    HRESULT SetTitle(LPCWSTR title);

    inline UINT GetWidth() { return _width; }
    inline UINT GetHeight() { return _height; }
    inline LPCWSTR GetTitle() { return _title; }
    inline HWND GetHandle() { return hWnd; }

    inline void SetOnCreateCallback(WEOnCreate fun) { _onCreate = fun; }
    inline void SetOnDestroyCallback(WEOnDestroy fun) { _onDestroy = fun; }
    inline void SetOnRenderCallback(WEOnRender fun) { _onRender = fun; }
    inline void SetOnResizeCallback(WEOnResize fun) { _onResize = fun; }
    inline void SetOnScreenResolutionChangeCallback(WEOnScreenResolutionChange fun) { _onResChange = fun; }

    inline void SetExtraAllocatedSpace(void *ptr) { extra = ptr; }
    inline void *GetExtraAllocatedSpace() { return extra; }

    inline void Terminate() { if (hWnd) DestroyWindow(hWnd); }

    static inline void SetErrorCallback(ErrorCallback fun) { _errorCallback = fun; }

    HRESULT Initialize();
    void RunMessageLoop();

    static HRESULT GlobalInitialize();
    static HRESULT GlobalTerminate();
private:
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

    static inline void throwError(Window *window, UINT id, LPCWSTR message) {
        if (_errorCallback)
            _errorCallback(window, id, message);
    }

    WEOnCreate _onCreate;
    WEOnDestroy _onDestroy;
    WEOnRender _onRender;
    WEOnResize _onResize;
    WEOnScreenResolutionChange _onResChange;

    static ErrorCallback _errorCallback;
    static LPCWSTR szClassName;
    static HINSTANCE hInstance;

    HWND hWnd;
    int _width, _height;
    LPCWSTR _title;
    void *extra;
};

#endif

Window.cpp:

#include "Window.h"
//Initialize static variables
ErrorCallback Window::_errorCallback = nullptr;
LPCWSTR Window::szClassName = L"WindowClass";
HINSTANCE Window::hInstance;

Window::Window(LPCWSTR title = L"Window", UINT width = 640, UINT height = 480) :
_onCreate(nullptr),
_onDestroy(nullptr),
_onRender(nullptr),
_onResize(nullptr),
hWnd(NULL),
extra(NULL), 
_width(width),
_height(height),
_title(title) {}

Window::~Window() {
    if (hWnd) {
        DestroyWindow(hWnd);
        hWnd = NULL;
    }
}

HRESULT Window::GlobalInitialize() {
    // Retreive hInstance
    hInstance = GetModuleHandle(NULL);
    if (!hInstance) {
        throwError(NULL, ERROR_FAILED_HINSTANCE, ERROR_FAILED_HINSTANCE_STR);
        return E_FAIL;
    }

    // Create window class
    WNDCLASSEX wcex = {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = Window::WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = sizeof(LONG_PTR);
    wcex.hInstance = hInstance;
    wcex.hbrBackground = NULL;
    wcex.lpszMenuName = NULL;
    wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
    wcex.lpszClassName = szClassName;

    if (!RegisterClassEx(&wcex)) {
        throwError(NULL, ERROR_FAILED_REGISTER, ERROR_FAILED_REGISTER_STR);
        return E_FAIL;
    }

    return S_OK;
}

HRESULT Window::GlobalTerminate() {
    if (UnregisterClass(szClassName, hInstance))
        return S_OK;
    else
        return E_FAIL;
}

void Window::RunMessageLoop() {
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

HRESULT Window::Initialize() {
    // Create the window
    hWnd = CreateWindow(
        szClassName,
        _title,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        _width,
        _height,
        NULL,
        NULL,
        hInstance,
        this
        );

    if (!hWnd) {
        throwError(this, ERROR_FAILED_CREATION, ERROR_FAILED_CREATION_STR);
        return E_FAIL;
    }

    // Show and render the window
    ShowWindow(hWnd, SW_SHOW);
    UpdateWindow(hWnd);

    return S_OK;
}

LRESULT CALLBACK Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    if (message == WM_CREATE) {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        Window *window = (Window *)pcs->lpCreateParams;
        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, PtrToUlong(window));

        if (window->_onCreate != nullptr)
            window->_onCreate(window);


    }

    Window *pWnd = reinterpret_cast<Window *>(static_cast<LONG_PTR>(GetWindowLongPtr(hWnd, GWLP_USERDATA)));
    HRESULT hr = S_OK;

    if (!pWnd) {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    switch (message) {
    case WM_PAINT:
    {
                     if (pWnd->_onRender)
                         hr = pWnd->_onRender(pWnd);
                     else
                         hr = S_OK;
    }
        break;
    case WM_SIZE:
    {
                    if (pWnd->_onResize)
                        hr = pWnd->_onResize(pWnd, LOWORD(lParam), HIWORD(lParam));
                    else
                        hr = S_OK;
    }
        break;
    case WM_DISPLAYCHANGE:
    {
                    if (pWnd->_onResChange)
                        hr = pWnd->_onResChange(pWnd, LOWORD(lParam), HIWORD(lParam));
                    else
                        hr = S_OK;
    }
        break;
    case WM_DESTROY:
    {
                    if (pWnd->_onDestroy && FAILED(pWnd->_onDestroy(pWnd)))
                        break;
    }
        PostQuitMessage(0);
        hWnd = NULL;
        break;
    default:
        hr = DefWindowProc(hWnd, message, wParam, lParam);
    }

    return hr;
}

HRESULT Window::SetSize(UINT width, UINT height) {
    if (hWnd)
    if (!::SetWindowPos(hWnd, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER))
        return E_FAIL;

    _width = width;
    _height = height;

    return S_OK;
}

HRESULT Window::SetTitle(LPCWSTR title) {
    if (hWnd)
    if (!::SetWindowText(hWnd, title))
        return E_FAIL;

    _title = title;

    return S_OK;
}

I hope someone can help me since everything looks OK(the window even runs fine).

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79

1 Answers1

4

Firstly, you seem to be treating the window procedure as if it was a COM method, but window procedures do not return an HRESULT - they return an LRESULT whose meaning is different for each message.

In the case of WM_PAINT it's not possible to return a value that indicates "I don't need to paint this time". The system will send WM_PAINT messages as long as a portion of your window is marked as dirty, and the way you mark the dirty area as "painted" is by calling BeginPaint and EndPaint. If you don't do this, the system will continue to consider your window as dirty and continue to send WM_PAINT messages.

You haven't shown the source code for your _onRender function but the very fact that you have made WM_PAINT handling optional (i.e. if nothing calls SetOnRenderCallback then no callback will be registered) means that you are probably not processing WM_PAINT correctly. At the very least, if you don't do the painting yourself, you should pass the message through to DefWindowProc to allow the default processing to take place.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79