CreateWindowEx 不使用 class 模板的实例创建 HWND

CreateWindowEx does not create HWND with the instance of a class template

我正在关注 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)。但是如果我尝试将定义移动到单独的文件中,它不会创建 HWND。

在调用 CreateWindowEx 时,它转到 BaseWindow::WindowProc 并发送以下消息,按顺序一个接一个:

它跳出后,导致HWND没有被创建。因此,不会出现 window。

项目结构:

这是我的代码的样子。

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;
}

我试过只在 类 上做,没有模板而且它有效。

我也尝试删除 CreateWindowEx 参数列表末尾的 this 并且它也有效!我的意思是,如果它看起来像那样,它就会起作用:

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

所以我可能在某种程度上滥用了模板。

HandleMessage()使用m_hwnd,未初始化。这就是为什么 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

简而言之:您不能使用 m_hwnd 直到 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.

CreateWindowEx return 之前将发送几条消息。除了一个 (WM_NCCREATE) 之外,所有的都将通过 HandleMessage 路由,它期望 m_hwnd 是一个有效的句柄。他们没有得到它,因为你永远不会保存它,直到 CreateWindowEx 的最终结果被 return 值获得。到时候就晚了。

WM_NCCREATE 是顶级 windows(例如你的)收到的第一条 window 消息,应该是用于 (a) 将 hwnd 参数保存到 m_hwnd,以及 (b) 存储实例指针 int GWLP_USERDATA space。你做的是后者,而你没有做前者。

CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
pThis->m_hwnd = hwnd; // <===== ADD THIS =====
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);