c++windowsclassoopframeworks

Programming an OOP based framework ("win32engine"), but CreateWindow(...) fails when passing 'this' into the last parameter of the function


I am using Visual Studio, C++ language, subsystem Windows.

I asked ChatGPT multiple times without success. I tried what he said but either it didn't work or he just repeated himself.

I realised that when I just pass NULL instead of this in the windowCreate() function in my constructor, the creation works and hWND is not NULL, but of course WindowProc doesn't work because pThis is NULL then.

Also this is the output I get plottet:

In Window Constructor
The operation completed successfully.

which is weird because it fails (hWND is NULL) but the error code is 0 (The operation completed successfully).

Can anyone help me?

code:

main.cpp:

#include "win32engine.h"    //engine

using namespace win32engine;

Window myWindow(L"Hello World Application");

int main()
{
    myWindow.SetVisibility(SW_SHOW); // Show the window

    OutputDebugString(L"Script running!\n"); // Output debug message
    return 0;
}

win32engine.h:

#pragma once

#include <windows.h>    // Windows API              (creating windows)
#include <windowsx.h>   // Windows API              (creating windows)
#include <stdint.h>     // Integer types            (standard types)
#include <wingdi.h>     // Windows GDI              (graphics device interface)

#include "window.h"
#include "input.h"
#include "renderer.h"
#include "application.h"

#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif

namespace win32engine {
    extern HINSTANCE hInstance;
}

application.h:

#pragma once
#include "win32engine.h"

namespace win32engine {
    extern HINSTANCE hInstance; // Global instance handle
    extern "C" int main();
}

application.cpp:

#include "application.h"

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugString(std::to_wstring(reinterpret_cast<uintptr_t>(hInst)).c_str());
    win32engine::hInstance = hInst; // Set global instance handle
    return win32engine::main();
}

HINSTANCE win32engine::hInstance = NULL; // Initialize global instance handle

window.h:

#pragma once

#include <windows.h>    // Windows API              (creating windows)
#include <windowsx.h>   // Windows API              (creating windows)
#include <stdint.h>     // Integer types            (standard types)
#include <wingdi.h>     // Windows GDI              (graphics device interface)

#include <string>
#include <cwchar>

#include "renderer.h"
#include "input.h"
#include "application.h"

namespace win32engine
{
    class Input;
    class Renderer;

    class Window {
    private:
        uint16_t width, height;
        HWND hWND = NULL;
        std::wstring className;
        LPCWSTR title;
    public:
        Window(LPCWSTR title);
        void SetVisibility(int state);
        void SetWindowSize(uint16_t w, uint16_t h);
        void SetRenderSize(uint16_t w, uint16_t h);
        //win32engine::Renderer Renderer();
        Input input;
        Renderer renderer;
    private:
        static LRESULT WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
        LRESULT HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam);
        bool isRegistered();
    };
}

window.cpp:

#include "window.h"

win32engine::Window::Window(LPCWSTR title) {
    HINSTANCE hInst = win32engine::hInstance;
    this->title = title;
    //className = L"win32engine_class_" + std::wstring(this->title);
    className = L"Hello";

    UnregisterClass(className.c_str(), hInst);
    /*WNDCLASSEX wc;
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.hCursor = LoadCursor(NULL, IDC_CROSS);
    wc.hInstance = hInst;
    wc.lpszClassName = className.c_str();
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpszMenuName = NULL;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.cbWndExtra = sizeof(Window);
    wc.lpfnWndProc = WindowProc;

    DWORD err = 0;
    if (!RegisterClassEx(&wc)) {
        err = GetLastError();

        wchar_t buf[512];
        FormatMessageW(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            err,
            0,
            buf,
            sizeof(buf) / sizeof(wchar_t),
            nullptr
        );

        swprintf_s(buf, L"%s (Error code: %lu)\n", buf, err);

        OutputDebugString(L"Failed to register window class: \n");
        OutputDebugString(buf);
    }
    else
        OutputDebugString(L"Succesfully registered window class\n");

    err = 0;
    hWND = CreateWindowEx(0, className.c_str(), title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, this);
    if (hWND == NULL) {
        err = GetLastError();
        OutputDebugString((std::to_wstring(err) + L"\n").c_str());
        wchar_t buf[512];
        FormatMessageW(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            err,
            0,
            buf,
            sizeof(buf) / sizeof(wchar_t),
            nullptr
        );

        swprintf_s(buf, L"%s (Error code: %lu)\n", buf, err);

        OutputDebugString(L"Failed to create window: \n");
        OutputDebugString(buf);
    }
    else {
        OutputDebugString(L"Window created successfully\n");
    }*/    
    WNDCLASSEX wc;
    memset(&wc, 0, sizeof(WNDCLASSEX)); // Clear the structure
    wc.cbSize = sizeof(wc);

    wc.hCursor = LoadCursor(NULL, IDC_CROSS);
    wc.hInstance = hInst;
    //wc.lpszClassName = className.c_str();
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    /*wc.cbWndExtra = sizeof(void*);

    OutputDebugString((L"Registering Class with name: \n\t\"" + className + L"\"\n").c_str());

    if (!RegisterClassEx(&wc))
        OutputDebugString(L"RegisterClassEx() failed\n");
    else
        OutputDebugString(L"RegisterClassEx() succeeded\n");

    hWND = CreateWindowEx(0, wc.lpszClassName, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, this);
    if (!hWND)
        OutputDebugString(L"CreateWindowEx() failed\n");
    else
        OutputDebugString(L"CreateWindowEx() succeeded\n");*/

    className = L"MyWindowClass";
    wc.lpszClassName = className.c_str();
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.lpszMenuName = NULL;
    wc.cbWndExtra = sizeof(void*);

    if (!RegisterClassEx(&wc))
        OutputDebugString(L"RegisterClassEx() failed\n");
    hWND = CreateWindowEx(0, className.c_str(), title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, this);
    if (!hWND) {
        DWORD err = GetLastError();
        wchar_t buf[512];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err, 0, buf, sizeof(buf) / sizeof(wchar_t), nullptr);
        OutputDebugString(L"In Window Constructor\n");
        OutputDebugString(buf);
    }
    
    ShowWindow(hWND, 1);
    UpdateWindow(hWND);

    SetRenderSize(500, 500);

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

void win32engine::Window::SetVisibility(int state)
{
    if (!isRegistered())
        return;
    ShowWindow(hWND, state);
}

void win32engine::Window::SetWindowSize(uint16_t w, uint16_t h)
{
    if (!isRegistered())
        return;
    width = w; // Set width
    height = h; // Set height
    SetWindowPos(hWND, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}

void win32engine::Window::SetRenderSize(uint16_t w, uint16_t h)
{
    if (!isRegistered())
        return;
    RECT rect = { 0, 0, w, h };
    AdjustWindowRect(&rect, GetWindowLong(hWND, GWL_STYLE), FALSE);
    width = (uint16_t)(rect.right - rect.left);
    height = (uint16_t)(rect.bottom - rect.top);
    SetWindowPos(
        hWND,
        NULL,
        0, 0,
        rect.right - rect.left,
        rect.bottom - rect.top,
        SWP_NOZORDER | SWP_NOMOVE
    );
}

/*win32engine::Renderer& win32engine::Window::Renderer()
{
    return renderer;
}*/

LRESULT CALLBACK win32engine::Window::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    Window* pThis = nullptr;
    if (msg == WM_NCCREATE) {
        CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
        pThis = (Window*)cs->lpCreateParams;
        SetLastError(0);
        LONG_PTR prev = SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
        if (prev == 0) {
            DWORD err = GetLastError();
            if (err != 0) {
                wchar_t buf[512];
                FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err, 0, buf, sizeof(buf) / sizeof(wchar_t), nullptr);
                OutputDebugString(L"In WindowProc, message WM_NCCREATE\n");
                OutputDebugString(buf);
                return FALSE;
            }
        }
        pThis->hWND = hwnd;
    }
    else {
        // Zeiger aus UserData holen
        pThis = (Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    }

    if (pThis) {
        return pThis->HandleMessage(msg, wParam, lParam);
    }
    else {
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}


LRESULT win32engine::Window::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
    case WM_PAINT:
        renderer.redraw();
        break;
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_SETFOCUS:
        break;
    case WM_KILLFOCUS:
        input.ResetKeyStates();
        input.SetMouseState(Input::MouseState::NONE);
        break;
    case WM_KEYDOWN:
        input.SetKeyState((uint8_t)wParam, true);
        break;
    case WM_KEYUP:
        input.SetKeyState((uint8_t)wParam, false);
        break;
    case WM_MOUSEMOVE:
        input.SetMousePosition(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        break;
    case WM_LBUTTONDOWN:
        input.SetMouseState(Input::MouseState::LEFT_BUTTON);
        break;
    case WM_MBUTTONDOWN:
        input.SetMouseState(Input::MouseState::MIDDLE_BUTTON);
        break;
    case WM_RBUTTONDOWN:
        input.SetMouseState(Input::MouseState::RIGHT_BUTTON);
        break;
    case WM_LBUTTONUP:
        input.SetMouseState(Input::MouseState::NONE);
        break;
    case WM_MBUTTONUP:
        input.SetMouseState(Input::MouseState::NONE);
        break;
    case WM_RBUTTONUP:
        input.SetMouseState(Input::MouseState::NONE);
        break;
    case WM_MOUSEWHEEL:
        input.SetMouseScrollWheel(GET_WHEEL_DELTA_WPARAM(wParam)); // Set mouse wheel delta
        break;
    }
    return 0;
}
bool win32engine::Window::isRegistered()
{
    return hWND != NULL;
}

renderer.h:

#pragma once

#include <windows.h>    // Windows API              (creating windows)
#include <windowsx.h>   // Windows API              (creating windows)
#include <stdint.h>     // Integer types            (standard types)
#include <wingdi.h>     // Windows GDI              (graphics device interface)

namespace win32engine
{
    class Renderer
    {
    public:
        Renderer();
        void redraw();
    };
}

renderer.cpp:

#include "renderer.h"

namespace win32engine
{
    Renderer::Renderer()
    {

    }
    void Renderer::redraw()
    {
        
    }
}

input.h:

#pragma once

#include <windows.h>    // Windows API              (creating windows)
#include <windowsx.h>   // Windows API              (creating windows)
#include <stdint.h>     // Integer types            (standard types)
#include <wingdi.h>     // Windows GDI              (graphics device interface)

namespace win32engine
{
    class Input {
        friend class Window;
    private:
        bool key_states[256];
        bool key_states_old[256];
        uint16_t mouse_x, mouse_y;
        uint8_t mouse_state = NONE;
        int16_t mouse_wheel_delta;

    public:
        enum MouseState { NONE, LEFT_BUTTON, RIGHT_BUTTON, MIDDLE_BUTTON };

        Input();
        bool GetKeyHeld(uint8_t key);
        bool GetKeyStateChange(uint8_t key);
        bool GetKeyPressed(uint8_t key);
        bool GetKeyReleased(uint8_t key);
        uint16_t GetMouseX();
        uint16_t GetMouseY();
        uint16_t GetMouseWheelDelta();
        uint8_t GetMouseState();
    private:
        void SetMousePosition(int x, int y);
        void SetMouseState(MouseState state);
        void SetMouseScrollWheel(int delta);
        void SetKeyState(uint8_t key, bool state);
        void ResetKeyStates();
    };
}

input.cpp:

#include "input.h"

win32engine::Input::Input() {
    ResetKeyStates();
}
bool win32engine::Input::GetKeyHeld(uint8_t key) {
    return key_states[key];
}
bool win32engine::Input::GetKeyStateChange(uint8_t key) {
    return key_states[key] != key_states_old[key];
}
bool win32engine::Input::GetKeyPressed(uint8_t key) {
    return key_states[key] && !key_states_old[key];
}
bool win32engine::Input::GetKeyReleased(uint8_t key) {
    return !key_states[key] && key_states_old[key];
}
uint16_t win32engine::Input::GetMouseX() {
    return mouse_x;
}
uint16_t win32engine::Input::GetMouseY() {
    return mouse_y;
}
uint16_t win32engine::Input::GetMouseWheelDelta() {
    return mouse_wheel_delta;
}
uint8_t win32engine::Input::GetMouseState() {
    return mouse_state;
}
void win32engine::Input::SetMousePosition(int x, int y) {
    mouse_x = x;
    mouse_y = y;
}
void win32engine::Input::SetMouseState(MouseState state) {
    mouse_state = state;
}
void win32engine::Input::SetMouseScrollWheel(int delta) {
    mouse_wheel_delta = delta;
}
void win32engine::Input::SetKeyState(uint8_t key, bool state) {
    key_states_old[key] = key_states[key];
    key_states[key] = state;
}
void win32engine::Input::ResetKeyStates() {
    memset(key_states, 0, sizeof(key_states));
    memset(key_states_old, 0, sizeof(key_states_old));
}

I tried changing from WNDCLASS to WNDCLASSEX, to set wc.cbWndExtra to sizeof(void*) to allocate storage for the this pointer, I think??

Also, I tried switching from a class name, which is different for each window, to a single one.


Solution

  • If you pass this to CreateWindowEx(), then pThis is not NULL in WindowProc(), and you call pThis->HandleMessage().

    HandleMessage() returns 0 (FALSE) when processing WM_NCCREATE, which cancels the window creation:

    https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccreate

    If an application processes this message, it should return TRUE to continue creation of the window. If the application returns FALSE, the CreateWindow or CreateWindowEx function will return a NULL handle.

    HandleMessage() needs to call DefWindowProc() for any unhandled message, ie in a default clause of your switch block, like this:

    LRESULT win32engine::Window::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
        switch (msg) {
        ...
        default:
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
        return 0;
    } 
    

    DefWindowProc() returns 1 (TRUE) for WM_NCCREATE.