【问题标题】:WINAPI - I would like to have the message pump ongoing in a separate threadWINAPI - 我想让消息泵在一个单独的线程中进行
【发布时间】:2016-10-06 12:06:40
【问题描述】:

在 Windows API 中,制作窗口需要消息泵来保持窗口运行和更新。现在写一个message pump由一个while循环组成,它支配着整个程序,不允许执行其他的东西,这是个大问题。

考虑一下我的代码,它是一个名为CFrame.h 的头文件(因为在里面我创建了一个名为CFrame 的类,它旨在模仿Java 中的JFrame)。换句话说,我希望可以创建多个 CFrame 实例,以便显示多个窗口,并且消息循环不会在创建第一个窗口后停止窗口。

我为函数ThreadExecution()创建了一个新线程,由于某种原因程序刚刚终止,为什么?

#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();
    }
};

【问题讨论】:

  • 也许将您的工作转移到另一个线程而不是消息循环?
  • 你的问题是什么?
  • @appleapple:这很清楚。显示的消息泵没有返回,因此CFrame ctor 也没有返回,这就解释了为什么不能在同一个线程上创建两个 CFrame 对象。
  • @MSalters 好吧,我只想在 @MarkoCrush 中添加一些错误说明
  • 您的做法完全错误。了解系统的工作原理。不要与之抗争。

标签: c++ windows multithreading winapi


【解决方案1】:

在非main 线程上使用消息泵是完全可以的。但是,消息泵必须在创建窗口的线程上。在您的情况下,这意味着必须从同一个线程调用 CreateAWindowstartMessageLoop

【讨论】:

  • 然后我是否使用两次 CreateThread,一次用于 CreateAWindow,一次用于 startMessageLoop?
  • @MarkoCrush:不,这正是我所说的相反。使用 same 线程。此外,由于您正在编写 C++ 代码,因此请使用 std::thread,并在该线程上调用 .detach。 WINAPICreateThread是操作系统接口,不知道你是要在那个线程上运行FORTRAN、Java还是C++代码,所以一般需要封装。
  • 对不起,你的意思是让我创建一个线程来打包函数 CreateAWindow 和 startMessageLoop。我将使用 std::thread 进行尝试。谢谢。
  • std::thread t1(CreateAWindow); std::thread t1(startMessageLoop); t1.detach();这显然行不通,我如何将两者都放在 t1 中?
  • std::thread([]{new CFrame}).detach() 会工作。不要忘记在CFrame::~CFrame 中拨打delete this,您必须拨打该电话以回复WM_CLOSEWM_CLOSE 当然是由该线程内的消息泵处理的。
【解决方案2】:

现在,写一个消息泵由一个while循环组成,它支配着整个程序,不允许执行其他事情

传统的消息循环可能会以这种方式工作,但当然可以编写一个可以在消息之间执行其他操作的消息循环。谁说你不能使用消息本身来做事。您可能有一个阻塞调用线程的循环,但您可以控制该循环在每次迭代中实际执行的操作,因此它可以代表其调用线程执行操作。不需要处理消息。

换句话说,我希望可以创建多个 CFrame 实例,以便显示多个窗口,并且消息循环不会在创建第一个窗口后停止窗口。

任何类型的消息循环都可以在同一个线程中处理多个窗口。

我为函数ThreadExecution()创建了一个新线程,由于某种原因程序刚刚终止,为什么?

因为你的窗口管理是完全错误的。您对窗口和线程如何协同工作存在根本性的误解。您不会为每个窗口创建单独的线程(尽管您在技术上可以,但这样做很浪费)。您创建一个线程(或仅使用主线程)来创建多个窗口,然后使用单个消息循环来为它们提供服务。

试试这样的:

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

然后你可以在一个线程中做这样的事情:

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

等等……

【讨论】:

    【解决方案3】:

    std::thread 好像不行,你可以改用CreateThread


    (简单)示例代码

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

    【讨论】:

    • std::thread 很好。但是这段代码将是一场灾难。远离 UI 线程运行 UI 通常以痛苦告终。不要这样做。
    • @DavidHeffernan:只要有两个不同的 UI 线程,代码就很好。这里没有直接的问题,例如跨越线程边界的窗口层次结构。另一方面,像ShowWindow(hwnd, 5); 这样的声明确实让我担心。这是一个相当安全的猜测,作者从未觉得有必要阅读文档。
    • @MarkoCrush:有命名常量,所以我不必打开我的网络浏览器,导航到文档,然后弄清楚5 应该做什么。
    • @MarkoCrush:这没有多大意义。无论如何,如果您阅读其他人的代码,您希望看到哪个版本:ShowWindow(hWnd, 5);ShowWindow(hWnd, SW_SHOW);。我在这里有一个无可争议的最爱。如果你不这样做,一旦你加入你的第一个团队,你就会获得一些乐趣。
    • @MarkoCrush:“没有人会阅读那部分代码” - 将其发布在网站上,数以百万计的访问量是实现目标的好方法那个目标。您似乎通常不知道您的行为的影响。
    【解决方案4】:

    你可以移动

    • 创建窗口
    • 运行消息循环

    进入一个方法,并使用线程来执行这个方法


    • std::thread 好像不行,你需要CreateThread 代替

    • 当然,你可以把它封装在类中。


    当你正在创建游戏 API 时,也许你可以使用 PeekMessage(non-block) 并做你自己的计时器之类的?

    【讨论】:

    • 所以你建议我创建一个函数来调用这两个函数,并且那个函数会被一个线程调用?
    • @MarkoCrush 是的,而且更容易实现(我认为)
    • 我编辑了我的代码和问题,由于某种原因,当我按照你说的做时,程序就终止了。
    • @MarkoCrush 我不太确定是否可以在除主线程之外的不同线程上创建窗口,也许将您的“获取创建窗口命令”更改为另一个线程并使用发布/发送消息并驱动循环?
    • 欢迎窗口留在主线程中,我说的是消息循环,它应该在不同的线程中,因为它会干扰整个程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 2014-02-08
    • 2014-03-07
    • 2012-04-20
    • 2012-04-15
    • 1970-01-01
    • 2021-06-17
    相关资源
    最近更新 更多