简体   繁体   中英

WINAPI - I would like to have the message pump ongoing in a separate thread

In the Windows API, making a window requires a message pump to keep the window running and updated. Now, writing a message pump consists of a while loop, which dominates the entire program, not allowing other things to be executed, which is a big problem.

Consider my code, which is a header file that I called CFrame.h (because inside I made a class called CFrame which is meant to mimick JFrame in Java). In other words, I want it to be possible to create multiple instances of CFrame so that multiple windows show up and the message loop will not stop the windows after the first one from being created.

I made a new thread for the function ThreadExecution() , for some reason the program just terminates, why?

#define UNICODE

#include <windows.h>

const wchar_t CLASS_NAME[] = L"Window Class";
static int nWindows = 0; // Number of ongoing windows 

class Size { // Size of the window
private:
    int width;
    int height;
    public:
    Size(int width, int height) :width(width), height(height) {}
    int getWidth() {
        return width;
    }
    int getHeight() {
        return height;
    }
};

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_DESTROY: nWindows--; break;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void RegisterDetails(HINSTANCE hInstance) { // Registers WNDCLASS
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    RegisterClass(&wc);
}

void startMessageLoop() { // This is the message loop which must be in a    separate thread
    MSG msg;
    while (nWindows) {
        GetMessage(&msg, NULL, 0, 0);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

HWND CreateAWindow(LPCWSTR title, Size size, HINSTANCE hInstance) {
    if (nWindows == 0) { // The WNDCLASS only needs to be registered once
        RegisterDetails(hInstance);
    }
    HWND hwnd = CreateWindowEx(0, CLASS_NAME, title, WS_OVERLAPPEDWINDOW,     CW_USEDEFAULT, CW_USEDEFAULT, size.getWidth(), size.getHeight(), NULL, NULL, hInstance, NULL);
    ShowWindow(hwnd, 5);
    return hwnd;
}


void ThreadExecution(HWND hwnd, LPCWSTR title, Size size, HINSTANCE hInstance) {
    hwnd = CreateAWindow(title, size, hInstance);
    nWindows++;
    if (nWindows == 1) // If only one window has been created, the message loop will be called
    {
        startMessageLoop();
    }
}

class CFrame {

private:
    HINSTANCE hInstance;
    Size size;
    HWND hwnd;

public:
    CFrame() { 
    }

    CFrame(LPCWSTR title, Size size, HINSTANCE hInstance) :size(size), hInstance(hInstance) 
    { 
        std::thread t1(ThreadExecution, hwnd, title, size, hInstance);
        t1.detach();
    }
};

It's perfectly OK to have a message pump on a non- main thread. However, the message pump must be on the thread that created the window. In your case, that means that CreateAWindow and startMessageLoop must be called from the same thread.

Now, writing a message pump consists of a while loop, which dominates the entire program, not allowing other things to be executed

A traditional message loop might work that way, but it is certainly possible to write a message loop that can do other things in between messages. And who says that you can't use messages themselves to do things. You might have a loop that blocks the calling thread, but you control what that loop actually does on each iteration, so it can do things on behalf of its calling thread. It is not required to only process messages.

In other words, I want it to be possible to create multiple instances of CFrame so that multiple windows show up and the message loop will not stop the windows after the first one from being created.

Any kind of message loop can handle multiple windows in the same thread just fine.

I made a new thread for the function ThreadExecution() , for some reason the program just terminates, why?

Because your window management is completely wrong. You have a fundamental misunderstanding of how windows and threads work together. You don't create a separate thread for each window (though you technically can, but it is wasteful to do so). You create one thread (or just use your main thread) to create multiple windows, and then use a single message loop to service them.

Try something more like this:

#ifndef CFrameH
#define CFrameH

#include <windows.h>

class Size { // Size of the window
private:
    int width;
    int height;
public:
    Size(int width, int height);

    int getWidth() const;
    int getHeight() const;
};

class CFrame {
private:
    HINSTANCE hInstance;
    Size size;
    HWND hwnd;

    virtual LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);

    static void RegisterDetails(HINSTANCE);
    static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

public:
    CFrame(LPCWSTR title, Size size, HINSTANCE hInstance);
    ~CFrame();
};

#endif

CFrame.cpp

#include "CFrame.h"

static LPCWSTR CLASS_NAME = L"Window Class";

Size::Size(int width, int height)
    : width(width), height(height)
{
}

int Size::getWidth() const {
    return width;
}

int Size::getHeight() const {
    return height;
}

CFrame::CFrame(LPCWSTR title, Size size, HINSTANCE hInstance)
    : size(size), hInstance(hInstance) 
{ 
    RegisterDetails(hInstance);

    hwnd = CreateWindowExW(0, CLASS_NAME, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, size.getWidth(), size.getHeight(), NULL, NULL, hInstance, this);
    if (hwnd) {
        ShowWindow(hwnd, SW_SHOW);
    }
}

CFrame::~CFrame()
{ 
    if (hwnd)
        DestroyWindow(hwnd);
}

void CFrame::RegisterDetails(HINSTANCE hInstance) { // Registers WNDCLASS
    WNDCLASSW wc = {};
    BOOL bRegistered = GetClassInfoW(hInstance, CLASS_NAME, &wc);
    if ((!bRegisterd) || (wc.lpfnWndProc != &WindowProc)) {
        if (bRegistered) {
          UnregisterClassW(CLASS_NAME, hInstance);
        }
        wc.lpfnWndProc = &WindowProc;
        wc.hInstance = hInstance;
        wc.lpszClassName = CLASS_NAME;
        RegisterClassW(&wc);
    }
}

LRESULT CALLBACK CFrame::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    CFrame *pThis;

    if (uMsg == WM_CREATE) {
        pThis = (CFrame*) ((CREATESTRUCT*)lParam)->lpCreateParams;
        SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR) pThis);
    } else {
        pThis = (CFrame*) GetWindowLongPtr(hwnd, GWL_USERDATA);
    }

    if (pThis)
        return pThis->WndProc(uMsg, wParam, lParam);

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}        

LRESULT CFrame::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (uMsg == WM_NCDESTROY) {
        hwnd = NULL;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Then you can do things like this in a thread:

void ThreadExecution(HINSTANCE hInstance) {
    CFrame frame1(L"frame1", Size(10, 10), hInstance);
    CFrame frame2(L"frame2", Size(20, 20), hInstance);

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

void ThreadExecution(HINSTANCE hInstance) {
    CFrame frame1(L"frame1", Size(10, 10), hInstance);
    CFrame frame2(L"frame2", Size(20, 20), hInstance);

    MSG msg;
    while (...) {
        if (PeekMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else {
            // do something else
        }
    }
}

void ThreadExecution(HINSTANCE hInstance) {
    CFrame frame1(L"frame1", Size(10, 10), hInstance);
    CFrame frame2(L"frame2", Size(20, 20), hInstance);

    // array of event/IO objects that are signaled
    // when things needs to be done...
    HANDLE hObjects[...];
    DWORD dwNumObjects = ...;
    ...

    MSG msg;
    while (...) {
        DWORD dwRet = MsgWaitForMultipleObjects(dwNumObjects, hObjects, FALSE, INFINITE, QS_ALLINPUT);
        if ((dwRet >= WAIT_OBJECT_0) && (dwRet < (WAIT_OBJECT_0+dwNumObjects))) {
            dwRet -= WAIT_OBJECT_0;
            // do something with hObjects[dwRet] ...
        }
        else if (dwRet == (WAIT_OBJECT_0+dwNumObjects)) {
            while (PeekMessage(&msg, NULL, 0, 0)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        ...
    }
}

And so on...

std::thread seems not work, you can use CreateThread instead


(Simple) Sample Code

#define UNICODE
#include <windows.h>

const wchar_t CLASS_NAME[] = L"Window Class";

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI CreateWindowAndRunUseMesageLoop(LPVOID* id){
    WCHAR className[] = L"XCLASSSSS";
    WCHAR title[] = L"XTITLE";
    title[0] = *(WCHAR*)id;
    className[0] = *(WCHAR*)id;
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = className;
    RegisterClass(&wc);

    auto hwnd = CreateWindowEx(0, className, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT
        , 300, 300, NULL, NULL, GetModuleHandle(NULL), NULL);

    ShowWindow(hwnd, SW_SHOW);

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

int main(){
    HANDLE handle[2];
    WCHAR i = L'0';
    handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateWindowAndRunUseMesageLoop, &i, 0, NULL);
    WCHAR j = L'1';
    handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateWindowAndRunUseMesageLoop, &j, 0, NULL);
    WaitForMultipleObjects(2, handle, TRUE, INFINITE);
}

You can move

  • create window
  • run message loop

into a method, and use thread to execute this method


  • std::thread seems not work, you need CreateThread instead

  • Of course, you can encapsulate this in class.


As you are create game API, maybe you can use PeekMessage (non-block) and do your own timer or something?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM