c++中MFC消息机制,UI线程和工作线程,模式对话框原理

2023-05-16

消息机制
Windows程序是事件驱动,消息传递的,而消息分为队列消息和非队列消息。
1. 队列消息:
对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE, WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT, WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
2. 非队列消息
非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。 例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。

在讲消息的时候提到了系统消息队列和线程消息队列。系统中有一个系统消息队列,它由系统维护,队列消息会发到它那里,然后再发送到相应的线程消息队列里面。线程消息队列有相应的线程在维护,但并不是每个线程都有线程消息队列,仅当线程第一次调用GDI函数时系统给线程创建一个消息队列(例如MFC的UI线程)。还有消息队列是相对线程而言,而非进程。对于MFC的UI线程的线程消息队列有了消息之后,还会派送消息到消息对应窗口(消息结构体中有窗口句柄)。

参考博文:http://bbs.51cto.com/viewthread.php?tid=487054

MFC UI线程和工作线程
之前挺好奇,MFC程序的主线程一直在跑消息循环,一直在while里面,界面为什么不卡住还能响应?
这取决与PeekMessage(),OnIdle()等函数。
BOOL PeekMessage(…):该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构。和GetMessage()不一样的是,GetMessage()从线程消息队列获取消息,将消息从队列中移除,属于阻塞函数。当队列无消息时,GetMessage会等待下一条消息。而函数PeekMesssge是以查看的方式从队列中获取消息,可以不将消息从系统中移除,是非阻塞函数;当队列无消息时,返回FALSE,继续执行后续代码。
virtual BOOL OnIdle( LONG lCount ):执行空闲时间处理。不需要更多空闲时间返回0,否则返回非0。在这个空闲时间处理中将更新UI界面(比如工具栏按钮的enable和disable状态),删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。在下面可以看到对传入参数lCount的处理。

后来找到下面这篇博文,主要讲MFC的UI线程和工作线程,个人感觉讲得很清晰,借助博主贴的代码,也更好地理解了消息机制。

内容转载自:http://www.cnblogs.com/carekee/articles/3160209.html

MFC的AfxBeginThread提供了两个版本:

CWinThread* AFXAPI AfxBeginThread(
    AFX_THREADPROC pfnThreadProc,
    LPVOID pParam,
    int nPriority =THREAD_PRIORITY_NORMAL,
    UINT nStackSize = 0,
    DWORD dwCreateFlags = 0,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
CWinThread* AFXAPI AfxBeginThread(
    CRuntimeClass* pThreadClass,
    int nPriority = THREAD_PRIORITY_NORMAL, 
    UINT nStackSize = 0,
    DWORD dwCreateFlags = 0,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

第一个版本用来让人创建“工作线程”,第二个版本让人用来创建“UI线程”。
1. UI线程
继承CWinThread->启动线程->进入CWinThread::Run()。
CWinThread::Run()的实现:

int CWinThread::Run()
{
    ASSERT_VALID(this);
    _AFX_THREAD_STATE* pState = AfxGetThreadState();

    // for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;

    // acquire and dispatch messages until a WM_QUIT message is received.
    for (;;)
    {
        // phase1: check to see if we can do idle work
        while (bIdle &&!::PeekMessage(&(pState->m_msgCur),NULL, NULL, NULL, PM_NOREMOVE))
        {
             // call OnIdle while in bIdle state
             if (!OnIdle(lIdleCount++))
                 bIdle = FALSE; // assume "no idle" state
        }
        // phase2: pump messages while available
        do
        {
             // pump message, but quit on WM_QUIT
             if (!PumpMessage())
                 return ExitInstance();
             // reset "no idle" state after pumping "normal" message
             //if (IsIdleMessage(&m_msgCur))
             if (IsIdleMessage(&(pState->m_msgCur)))
             {
                 bIdle = TRUE;
                 lIdleCount = 0;
             }
        } while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
    }
}

2.工作线程
对于工作线程,AfxBeginThread()调用了CWinThread的如下构造函数:

WinThread::CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam)
{
    m_pfnThreadProc = pfnThreadProc;
    m_pThreadParams = pParam;
    CommonConstruct();
}

然后AfxBeginThread()同样用CWinThread::CreateThread创建线程。
不过进入了if结构的不同分支。

    // first -- check for simple worker thread
    DWORD nResult = 0;
    if (pThread->m_pfnThreadProc != NULL)
    {
        nResult = (*pThread->m_pfnThreadProc)(pThread->m_pThreadParams);
        ASSERT_VALID(pThread);
    }
    // else -- check for thread with message loop
    else if (!pThread->InitInstance())
    {
        ASSERT_VALID(pThread);
        nResult = pThread->ExitInstance();
    }
    else
    {
        // will stop after PostQuitMessage called
        ASSERT_VALID(pThread);
        nResult = pThread->Run();
    }

工作线程会进入if的第一个分支,直接调用我们传入的线程函数,而不再进入CWinThread::Run。

3.MFC中的UI线程与工作线程的异同:综上,我们可以看到,MFC里的UI线程里,CWinThread实现了一个消息循环,这是工作线程所不具备的。除此之外,差异之处很寥寥。

MFC模式对话框原理
金山WPS的面试官问过我,怎么实现一个模式对话框,当时说的是自己一个思路,今天看到一篇博文,主要讲MFC消息循环和消息泵,但读下来也很好理解MFC的模式对话框是怎么实现的。

参考博文:http://www.cnblogs.com/dubingsky/archive/0001/01/01/1511299.html

1.非对话框程序
过程:
_tWinMain->AfxWinMain->AfxWinInit->CWinThread::InitApplication->CWinThread::InitInstance->CWinThread::Run
跟上面UI线程相似,进入WinThread::Run的消息循环中。

2.对话框程序(MFC对话框程序的主窗口就是一个模式对话框)
基于对话框的MFC工程和上面的消息循环机制不一样。它和上面讲到的非对话框程序的不同之处,主要在于应用程序对象的InitInstance()不一样。

//dlg_5Dlg.cpp 
BOOL CDlg_5App::InitInstance() 

    AfxEnableControlContainer(); 
    #ifdef _AFXDLL 
        Enable3dControls(); // Call this when using MFC in a shared DLL 
    #else 
    Enable3dControlsStatic(); // Call this when linking to MFC statically 
    #endif 

    CDlg_5Dlg dlg; //定义一个对话框对象 
    m_pMainWnd = &dlg; 
    int nResponse = dlg.DoModal(); //对话框的消息循环在这里面开始 
    if (nResponse == IDOK) 
    { 
    // TODO: Place code here to handle when the dialog is 
    // dismissed with OK 
    } 
    else if (nResponse == IDCANCEL) 
    { 
    // TODO: Place code here to handle when the dialog is 
    // dismissed with Cancel 
    } 

    // Since the dialog has been closed, return FALSE so that we exit the 
    // application, rather than start the application's message pump. 
    return FALSE; 

/*
NOTE: InitInstance函数返回FALSE,由最上面程序启动流程可以看出,CWinThread::Run是不会得到执行的。也就是说,上面第一部分说的消息循环在对话框中是不能执行的。实际上,对话框也有消息循环,她的消息循环在CDialog::DoModal()虚函数中的一个RunModalLoop函数中。 
*/
//这个函数的实现体在CWnd类中: 
int CWnd::RunModalLoop(DWORD dwFlags) 

    ASSERT(::IsWindow(m_hWnd)); // window must be created 
    ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state 

    // for tracking the idle time state 
    BOOL bIdle = TRUE; 
    LONG lIdleCount = 0; 
    BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE); 
    HWND hWndParent = ::GetParent(m_hWnd); 
    m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL); 
    MSG* pMsg = &AfxGetThread()->m_msgCur; 

    // acquire and dispatch messages until the modal state is done 
    for (;;) 
    { 
        ASSERT(ContinueModal()); 

        // phase1: check to see if we can do idle work 
        while (bIdle && !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE)) 
        { 
            ASSERT(ContinueModal()); 

            // show the dialog when the message queue goes idle 
            if (bShowIdle) 
            { 
                ShowWindow(SW_SHOWNORMAL); 
                UpdateWindow(); 
                bShowIdle = FALSE; 
            } 

            // call OnIdle while in bIdle state 
            if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0) 
            { 
                // send WM_ENTERIDLE to the parent 
                ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd); 
            } 
            if ((dwFlags & MLF_NOKICKIDLE) || !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++)) 
            { 
            // stop idle processing next time 
                bIdle = FALSE; 
            } 
        } 

        // phase2: pump messages while available 
        do 
        { 
            ASSERT(ContinueModal()); 

            // pump message, but quit on WM_QUIT 
            //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。 
            if (!AfxGetThread()->PumpMessage()) 
            { 
                AfxPostQuitMessage(0); 
                return -1; 
            } 

            // show the window when certain special messages rec'd 
            if (bShowIdle && (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN)) 
            { 
                ShowWindow(SW_SHOWNORMAL); 
                UpdateWindow(); 
                bShowIdle = FALSE; 
            } 

            if (!ContinueModal()) 
                goto ExitModal; 

            // reset "no idle" state after pumping "normal" message 
            if (AfxGetThread()->IsIdleMessage(pMsg)) 
            { 
                bIdle = TRUE; 
                lIdleCount = 0; 
            } 

        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE)); 
    } //无限循环 

ExitModal: 
    m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL); 
    return m_nModalResult; 
}


3.模式对话框和非模式对话框
模式对话框有自己的特殊消息循环;而非模式对话框,共用程序的消息循环,和普通的窗口已经没有什么大的区别了。 其实两者都是创建了非模式对话框,只是模式对话框作了更多的工作,包括使父窗口无效,然后进入自己的消息循环等等。

再加一点自己的理解,模式对话框或者非模式对话框都是与父窗口工作在同一个线程里面,那么一个父窗口在创建了一个模式对话框后,那原本的消息循环应该处于一直在执行新窗口的消息循环的状态中,如果不加处理,父窗口是会卡在后面的,但父窗口并没有处于转圈圈的状态,所以模式对话框是加了某些处理了,使得不响应对父窗口的点击事件,它们处于同个TLS中,要捕获和判断点击事件应该是不难实现的。而且要意识到,一个线程中,此时只有一个线程消息队列,2个消息循环,实际在跑的消息循环是模式对话框,那么捕获父窗口消息是可行的。这是自己的一点见解,如果有误,希望能得到各位的指出。
————————————————
版权声明:本文为CSDN博主「Jiangislogining」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Jiangislogining/article/details/52887175/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++中MFC消息机制,UI线程和工作线程,模式对话框原理 的相关文章

  • Qt QList清空问题

    1 代码分析 向QList中添加4个项 QList lt QWidget gt lstWidget lstWidget append new QWidget lstWidget append new QWidget lstWidget ap
  • Qt 信号和槽及第五个参数详解(线程相关)

    注意 xff1a 信号发送给信号容易阻塞 xff0c 需要设置好QObject Connect函数中第五个参数 xff0c Qt DirectConnection xff1a 发送的信号在哪个线程则接收方就运行在哪个线程上 前言 信号槽是Q
  • 研华PCI1716L的C#编程

    新建windows窗体项目 xff1b 添加引用研华的库文件 将库添加using 整个代码如下 xff1a using System using System Collections Generic using System Compone
  • QT中关于类静态成员的编码格式错误(静态成员初始化必须放在.cpp文件中)

    qt中指针类型的单例 error LNK1169 找到一个或多个多重定义的符号错误原因 xff1a 因为创建了 h文件和 cpp文件 xff0c 但是静态成员却在 h文件中类的外面初始化了成员变量 只有仅存在 h文件声明定义时才能这么用 x
  • 8uftp怎么连到,8uftp怎么连到服务器

    8uftp是一款易用的FTP软件 xff0c 很多刚接触网站建设的人都会用到这个软件 但使用8uftp连接服务器相对来说较为繁琐 xff0c 很多小伙伴都希望能尽量节省工作时间 xff0c 那你就应该试试iis7 作为IIS7服务器管理工具
  • QT中QWIDGET动态增加控件

    QPushButton btn 61 new QPushButton this btn gt show QPushButton buttonTest 61 new QPushButton 动态创建按钮 buttonTest gt setTe
  • QT::::点击退出按钮,退出当前窗口(lamda表达式写法),实现点击按钮打开和关闭窗口

    点击退出按钮 xff0c 退出当前窗口 QObject connect ui pushButton 5 amp QPushButton pressed this QApplication app app gt exit 在这里两个函数一样的
  • C# 中线程同步使用信号量总结

    所谓线程同步 xff0c 就是多个线程在某个对象上执行等待 xff08 也可理解为锁定该对象 xff09 xff0c 直到该对象被解除锁定 C 中对象的类型分为引用类型和值类型 CLR在这两种类型上的等待是不一样的 我们可以简单地理解为在C
  • 对话框及窗体在多线程中的应用(阻塞和非阻塞)

    方式1 阻塞UI线程及消息循环 AutoResetEvent autoResetEvent 61 new AutoResetEvent false Task Factory StartNew 61 gt Form form 61 new F
  • Windows11装新环境问题

    系统重装 1 下载最新的老毛桃U盘启动盘制作工具 xff08 旧版的会不支持windows11启动项 xff09 xff0c 制作U盘启动盘 2 将电脑的bitLocker硬盘加密取消掉在所有设置安全加密里 xff0c 否则将无法启动提示硬
  • c#引用office组件库迁移源码问题

    1 当程序引用了office组件时 xff0c 需要安装与迁移的源码相同版本的offce Library才行 xff0c 否则会报异常 2 可以下载office三合一版快捷安装 xff0c vs在引用里的com选项卡里会自动识别到相应的of
  • TCP和UDP的发送缓冲区和接收缓冲区内存问题

    TCP协议是作用是用来进行端对端数据传送的 xff0c 那么就会有发送端和接收端 xff0c 在操作系统有两个空间即user space和kernal space 每个Tcp socket连接在内核中都有一个发送缓冲区和接收缓冲区 xff0
  • QtCreator修改项目构建目录

    使用QtCreator编译Qt项目时 xff0c 如有需求修改编译过程文件 xff08 即Makefile o exe等文件 xff09 存放目录 xff0c 简单在工具 gt 选项 gt 构建和运行中修改Default build dir
  • QT中删除信号于槽的连接

    如果是在UI里建立的 xff0c 那就在下面这个函数里删除连接槽函数的对应行 void MainForm qt static metacall QObject o QMetaObject Call c int id void a if c
  • C++位操作中按位置0、置1、取反操作

    一 指定的某一位数置1 宏 define setbit x y x 61 1 lt lt y 二 指定的某一位数置0 宏 define clrbit x y x amp 61 1 lt lt y 三 指定的某一位数取反 宏 define r
  • LED恒流驱动IC汇总

    这几天在找LED恒流驱动芯片 xff0c 无意间在LED网论坛上发现这个帖子 xff0c 分享给大家 xff01 LED恒流IC芯片大盘点 韩国LDT LD1016 16位最大90mA LED屏幕 护栏灯管恒流驱动IC LD1048 48位
  • qt C++中指针自动释放内存及程序中的内存操作、管理

    程序加载到内存后代码存储到代码区 xff0c 并将全局变量 静态变量初始化到全局 静态内存区 xff0c 然后会分配2M左右的栈内存区用于存储局部变量 xff0c 并在运行时根据需要可以在堆内存区 空闲内存区及硬盘的虚拟内存区 申请空间 程
  • sqlite数据库文件提示损坏修复方法

    第1章 说明 1 1 下载SQLite Tools 1 2 运行 2 注意 xff1a 为了方便 xff0c 可把要修复的数据库文件直接放到sqlite3 exe路径下然后运行sqlite3 exe就不用输入具体路径了 sqlite3 ex
  • 常用的dos网络命令总结

    一 ping 主要是测试本机TCP IP协议配置正确性与当前网络现状 ping命令的基本使用格式是 xff1a ping IP地址 主机名 域名 t a n count l size t xff1a 连续对IP地址 主机名 域名执行Ping
  • C#中隐藏窗体并执行窗体逻辑的方法

    c 隐藏窗体方方法 this WindowState 61 FormWindowState Minimized this ShowInTaskbar 61 false base SetVisibleCore true 示例如下 Task F

随机推荐