0

I'm following this tutorial and if I keep all the definitions in a single header file, it's all fine and works (I mean, if I directly copy this code). But if I try to move the definitions into separate files, it won't create HWND.

On CreateWindowEx called, it goes to the BaseWindow<DERIVED_TYPE>::WindowProc and sends the following messages, one after the other in a sequence:

  • WM_GETMINMAXINFO
  • WM_NCCREATE
  • WM_NCDESTROY

After it steps out, it results in HWND not being created. Thus, no window will show up. HWND is NULL

The structure of the project:

  • main.cpp
  • Windows/
    • BaseWindow.h
    • BaseWindow.cpp
    • MainWindow.h
    • MainWindow.cpp

This is how my code looks.

main.cpp

#ifndef UNICODE
#define UNICODE
#endif // !UNICODE

#include <windows.h>
#include <new>

#include "Windows/MainWindow.h"

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    MainWindow win;

    if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
    {
        return 0;
    }

    ShowWindow(win.Window(), nCmdShow);

    // Run the message loop.

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

    return 0;
}

Windows/BaseWindow.h

#pragma once

#include <windows.h>

template <typename DERIVED_TYPE>
class BaseWindow
{
public:
    BaseWindow() : m_hwnd(NULL) { }
    
    static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    BOOL Create(
        PCWSTR lpWindowName,
        DWORD dwStyle,
        DWORD dwExStyle = 0,
        int x = CW_USEDEFAULT,
        int y = CW_USEDEFAULT,
        int nWidth = CW_USEDEFAULT,
        int nHeight = CW_USEDEFAULT,
        HWND hWndParent = 0,
        HMENU hMenu = 0
    );

    HWND Window() const { return m_hwnd; }

protected:

    virtual PCWSTR ClassName() const = 0;
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;

    HWND m_hwnd;
};

Windows/BaseWindow.cpp

#include "BaseWindow.h"
#include "MainWindow.h"

template <typename DERIVED_TYPE>
LRESULT CALLBACK BaseWindow<DERIVED_TYPE>::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    DERIVED_TYPE* pThis = NULL;

    if (uMsg == WM_NCCREATE)
    {
        CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
        pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
    }
    else
    {
        pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    }

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

template <typename DERIVED_TYPE>
BOOL BaseWindow<DERIVED_TYPE>::Create(
    PCWSTR lpWindowName,
    DWORD dwStyle,
    DWORD dwExStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu
)
{
    WNDCLASS wc = { 0 };

    wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = ClassName();

    RegisterClass(&wc);

    m_hwnd = CreateWindowEx(
        dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
        nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
    );

    return (m_hwnd ? TRUE : FALSE);
}

// !The definition of the class template
template class BaseWindow<MainWindow>;

Windows/MainWindow.h

#pragma once

#include "BaseWindow.h"

class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};

Windows/MainWindow.cpp

#include "MainWindow.h"

LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(m_hwnd, &ps);
        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(m_hwnd, &ps);
    }
    return 0;

    default:
        return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    return TRUE;
}

I've tried to do it on mere classes without templating and it works.

I've also tried to remove this at the end of the argument list of CreateWindowEx and it worked too! I mean, if it looks like that, it will work:

m_hwnd = CreateWindowEx(
    dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
    nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), NULL
);

So maybe I'm somehow misusing templates.

SkyFlame
  • 35
  • 5
  • 2
    Based just on the division of code and the use of templates I'm amazed it even *builds*, mainly due to this: [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). And fyi, `MainWindow.h` shouldn't be needed in `BaseWindow.cpp` (which shouldn't even exist if you get the jist of the article I just linked). – WhozCraig Jan 20 '22 at 10:44
  • @WhozCraig It builds because of `template class BaseWindow;` at the tail of the cpp file, but it will work only for `BaseWindow`. So why using template ??? – Bolo Jan 20 '22 at 10:49

2 Answers2

3

Before CreateWindowEx returns several messages will be pumped. All but one (WM_NCCREATE) will route through HandleMessage, which expects m_hwnd to be a valid handle. They're not getting that because you never save it until the final result of CreateWindowEx is reaped by return value. By then it is too late.

WM_NCCREATE is the first window message received by top-level windows (e.g. yours), and should be the one used to (a) save the hwnd parameter to m_hwnd, and (b) store the instance pointer int GWLP_USERDATA space. You're doing the latter, but you didn't do the former.

CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
pThis->m_hwnd = hwnd; // <===== ADD THIS =====
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • 1
    *"several messages will be pumped"* - This can easily be mis-read as meaning, that a message loop were involved. More precise wording would be, that the messages are sent. Sent messages that do not cross threads (as is the case here) bypass the message loop entirely, and the window procedure is called directly. *"`WM_NCCREATE` is the first window message received [...]"* - Dialogs receive a `WM_GETMINMAXINFO` prior to `WM_NCCREATE`, though the first message sent isn't contractual, i.e. subject to change. A local CBT hook can be used to implement a solution that's invariant to message ordering. – IInspectable Jan 20 '22 at 11:48
  • 2
    Also, another pet peeve of mine: `GWLP_USERDATA` has entirely ambiguous ownership semantics, and is best not used at all. If you are the implementer of a window class (i.e. you are calling `RegisterClassExW`), set the `cbWndExtra` to `sizeof void*` and use that. If you are the consumer of a window class (i.e. you are calling `CreateWindowExW`) use `SetPropW`/`GetPropW`. That leaves `GWLP_USERDATA` to the authors of reckless power tools to fight over. – IInspectable Jan 20 '22 at 12:03
2

HandleMessage() uses m_hwnd, which is not initialized. That is why CreateWindowEx() returns NULL.

1. CreateWindowEx
2. sends WM_CREATE
3. WM_CREATE is processed by WindowProc
4. WindowProc calls HandleMessage, which uses m_hwnd,
   which is not initialized, so HandleMessage returns
   failure
5. Failure is returned as the result for WM_CREATE,
   cancelling window creation
6. CreateWindowEx fails and returns NULL <--- and right now
   m_hwnd is assigned

In short: you cannot use m_hwnd until CreateWindowEx() returns.

WM_CREATE reference

If an application processes this message, it should return zero to continue creation of the window. If the application returns –1, the window is destroyed and the CreateWindowEx or CreateWindow function returns a NULL handle.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
rafix07
  • 20,001
  • 3
  • 20
  • 33