MFC消息循环和消息泵

 
分类: MFC 461人阅读 评论(0) 收藏 举报

      首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage)、消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情。在MFC的应用程式中(应用程式类基于CWinThread继承),必须要有一个消息循环,它的作用是从应用程式的消息队列中读取消息,并把他派送出去(::DispatchMessage)。而消息路由是指消息派送出去之后,系统(USER32.DLL)把消息投递到哪个窗口,连同以后消息在窗口之间的传递是怎样的。

      消息分为队列消息(进入线程的消息队列)和非队列消息(不进入线程的消息队列)。对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息;更有例如:WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程式就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统负责把消息加入到相应线程的消息队列中,于是就有了消息循环(从消息队列中读取并派送消息)。另有一种是非队列消息,他绕过系统队列和消息队列,直接将消息发送到窗口过程。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。创建窗口时发送WM_CREATE消息。在后面您将看到,MS这么设计是很有道理的,连同他的整套实现机制。

      这里讲述MFC的消息循环,消息泵。先看看程式启动时,怎么进入消息循环的:

[cpp] view plain copy print ?
  1. _tWinMain   
  2. ->AfxWinMain                    // main函数入口   
  3. ->AfxWinInit                    // AFX内部初始化   
  4. ->CWinThread::InitApplication   // AFX的全局初始化   
  5. ->CWinThread::InitInstance      // 线程初始化   
  6. ->CWinThread::Run               // mfc的消息处理中心  
_tWinMain ->AfxWinMain // main函数入口 ->AfxWinInit // AFX内部初始化 ->CWinThread::InitApplication // AFX的全局初始化 ->CWinThread::InitInstance // 线程初始化 ->CWinThread::Run // mfc的消息处理中心

     非对话框程式的消息循环的事情都从这CWinThread的一Run开始...

     第一部分:非对话框程式的消息循环机制

[cpp:nogutter] view plain copy print ?
  1. //thrdcore.cpp   
  2. // main running routine until thread exits   
  3. int CWinThread::Run()  
  4. {  
  5.    ASSERT_VALID(this);  
  6.   
  7.    // for tracking the idle time state   
  8.    BOOL bIdle = TRUE;  
  9.    LONG lIdleCount = 0;  
  10.   
  11.    // acquire and dispatch messages until a WM_QUIT message is received.   
  12.    for (;;)  
  13.    {  
  14.       // phase1: check to see if we can do idle work   
  15.       while (bIdle &&  
  16.             !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))  
  17.       {  
  18.          // call OnIdle while in bIdle state   
  19.          if (!OnIdle(lIdleCount++))  
  20.             bIdle = FALSE; // assume "no idle" state   
  21.       }  
  22.   
  23.      // phase2: pump messages while available   
  24.      do  
  25.      {  
  26.         // pump message, but quit on WM_QUIT   
  27.         if (!PumpMessage())  
  28.            return ExitInstance();  
  29.   
  30.         // reset "no idle" state after pumping "normal" message   
  31.         if (IsIdleMessage(&m_msgCur))  
  32.         {  
  33.            bIdle = TRUE;  
  34.            lIdleCount = 0;  
  35.         }  
  36.   
  37.      }   
  38.      while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));  
  39.    }    //无限循环,退出条件是收到WM_QUIT消息。   
  40.   
  41.    ASSERT(FALSE);  // not reachable   
  42. }  
//thrdcore.cpp // main running routine until thread exits int CWinThread::Run() { ASSERT_VALID(this); // 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(&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)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } //无限循环,退出条件是收到WM_QUIT消息。 ASSERT(FALSE); // not reachable } 这是个无限循环,他的退出条件是收到WM_QUIT消息:
[cpp] view plain copy print ?
  1. if (!PumpMessage())  
  2.     return ExitInstance();  
if (!PumpMessage()) return ExitInstance(); 在PumpMessage中,假如收到WM_QUIT消息,那么返回FALSE,所以ExitInstance()函数执行,跳出循环,返回程式的退出代码。所以,一个程式要退出,只用在代码中调用函数VOID PostQuitMessage( int nExitCode )。指定退出代码nExitCode就能够退出程式。

     下面讨论一下这个函数Run的流程,分两步:

     1、第一个内循环phase1。bIdle代表程式是否空闲。他的意思就是,假如程式是空闲并且消息队列中没有要处理的消息,那么调用虚函数OnIdle进行空闲处理。在这个处理中将更新UI界面(比如工具栏按钮的enable和disable状态),删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。OnIdle是能够重载的,您能够重载他并返回TRUE使消息循环继续处于空闲状态。

     NOTE:MS用临时对象是出于效率上的考虑,使内存有效利用,并能够在空闲时自动撤销资源。关于由句柄转换成对象,能够有若干种方法。一般是先申明一个对象obj,然后使用obj.Attatch来和一个句柄绑定。这样产生的对象是永久的,您必须用obj.Detach来释放对象。

    2、第二个内循环phase2。在这个循环内先启动消息泵(PumpMessage),假如不是WM_QUIT消息,消息泵将消息发送出去(::DispatchMessage)。消息的目的地是消息结构中的hwnd字段所对应的窗口。

[cpp:nogutter] view plain copy print ?
  1. //thrdcore.cpp   
  2. BOOL CWinThread::PumpMessage()  
  3. {  
  4.    ASSERT_VALID(this);  
  5.    
  6.    //假如是WM_QUIT就退出函数(return FALSE),这将导致程式结束.   
  7.    if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))   
  8.    {  
  9. #ifdef _DEBUG   
  10.       if (afxTraceFlags & traceAppMsg)  
  11.          TRACE0("CWinThread::PumpMessage - Received WM_QUIT./n");  
  12.       m_nDisablePumpCount++; // application must die   
  13.       // Note: prevents calling message loop things in 'ExitInstance'   
  14.       // will never be decremented   
  15. #endif   
  16.       return FALSE;  
  17.   }  
  18. #ifdef _DEBUG   
  19.   if (m_nDisablePumpCount != 0)  
  20.   {  
  21.      TRACE0("Error: CWinThread::PumpMessage called when not permitted./n");  
  22.      ASSERT(FALSE);  
  23.   }  
  24. #endif   
  25.   
  26. #ifdef _DEBUG   
  27.   if (afxTraceFlags & traceAppMsg)  
  28.      _AfxTraceMsg(_T("PumpMessage"), &m_msgCur);  
  29. #endif   
  30.   
  31.   // process this message   
  32.   if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))  
  33.   {  
  34.      ::TranslateMessage(&m_msgCur); //键转换   
  35.       ::DispatchMessage(&m_msgCur); //派送消息   
  36.   }  
  37.   return TRUE;  
  38. }  
//thrdcore.cpp BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); //假如是WM_QUIT就退出函数(return FALSE),这将导致程式结束. if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { #ifdef _DEBUG if (afxTraceFlags & traceAppMsg) TRACE0("CWinThread::PumpMessage - Received WM_QUIT./n"); m_nDisablePumpCount++; // application must die // Note: prevents calling message loop things in 'ExitInstance' // will never be decremented #endif return FALSE; } #ifdef _DEBUG if (m_nDisablePumpCount != 0) { TRACE0("Error: CWinThread::PumpMessage called when not permitted./n"); ASSERT(FALSE); } #endif #ifdef _DEBUG if (afxTraceFlags & traceAppMsg) _AfxTraceMsg(_T("PumpMessage"), &m_msgCur); #endif // process this message if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); //键转换 ::DispatchMessage(&m_msgCur); //派送消息 } return TRUE; } 在这一步有一个特别重要的函数大家一定认识:PreTranslateMessage。这个函数在::DispatchMessage发送消息到窗口之前,进行对消息的预处理。PreTranslateMessage函数是CWinThread的成员函数,大家重载的时候都是在View类或主窗口类中,那么,他是怎么进入别的类的呢?代码如下:
[cpp:nogutter] view plain copy print ?
  1. //thrdcore.cpp   
  2. BOOL CWinThread::PreTranslateMessage(MSG* pMsg)  
  3. {  
  4.    ASSERT_VALID(this);  
  5.   
  6.    // 假如是线程消息,那么将会调用线程消息的处理函数   
  7.    if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg))  
  8.       return TRUE;  
  9.   
  10.    // walk from target to main window   
  11.    CWnd* pMainWnd = AfxGetMainWnd();  
  12.    if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))  
  13.       return TRUE;  
  14.   
  15.    // in case of modeless dialogs, last chance route through main   
  16.    //   window's accelerator table   
  17.    if (pMainWnd != NULL)  
  18.    {  
  19.       CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);  
  20.       if (pWnd->GetTopLevelParent() != pMainWnd)  
  21.          return pMainWnd->PreTranslateMessage(pMsg);  
  22.    }  
  23.    return FALSE;   // no special processing   
  24. }  
//thrdcore.cpp BOOL CWinThread::PreTranslateMessage(MSG* pMsg) { ASSERT_VALID(this); // 假如是线程消息,那么将会调用线程消息的处理函数 if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg)) return TRUE; // walk from target to main window CWnd* pMainWnd = AfxGetMainWnd(); if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg)) return TRUE; // in case of modeless dialogs, last chance route through main // window's accelerator table if (pMainWnd != NULL) { CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd); if (pWnd->GetTopLevelParent() != pMainWnd) return pMainWnd->PreTranslateMessage(pMsg); } return FALSE; // no special processing } 由上面这个函数能够看出:

 

      第一,假如(pMsg->hwnd == NULL),说明这是个线程消息。调用CWinThread::DispatchThreadMessageEx到消息映射表找到消息入口,然后调用消息处理函数。

      NOTE: 一般用PostThreadMessage函数发送线程之间的消息,他和窗口消息不同,需要指定线程id,消息激被系统放入到目标线程的消息队列中;用ON_THREAD_MESSAGE( message, memberFxn )宏能够映射线程消息和他的处理函数。这个宏必须在应用程式类(从CWinThread继承)中,因为只有应用程式类才处理线程消息。假如您在别的类(比如视图类)中用这个宏,线程消息的消息处理函数将得不到线程消息。

      第二,消息的目标窗口的PreTranslateMessage函数首先得到消息处理权,假如函数返回FALSE,那么他的父窗口将得到消息的处理权,直到主窗口;假如函数返回TRUE(表示消息已被处理了),那么就无需调用父类的PreTranslateMessage函数。这样,确保了消息的目标窗口连同他的父窗口都能够有机会调用PreTranslateMessage--在消息发送到窗口之前进行预处理(假如自己处理完然后返回FALSE的话 -_-b),假如您想要消息不传递给父类进行处理的话,返回TRUE就行了。

     第三,假如消息的目标窗口和主窗口没有父子关系,那么再调用主窗口的PreTranslateMessage函数。为什么这样?由第二步知道,一个窗口的父窗口不是主窗口的话,尽管他的PreTranslateMessage返回FALSE,主窗口也没有机会调用PreTranslateMessage函数。我们知道,加速键的转换一般在框架窗口的PreTranslateMessage函数中。
      我找遍了MFC中关于加速键转换的处理,只有CFrameWnd,CMDIFrameWnd,CMDIChildWnd等窗口类有。所以,第三步的意思是,假如消息的目标窗口(他的父窗口不是主窗口,比如一个这样的非模式对话框)使消息的预处理继续漫游的话(他的PreTranslateMessage返回FALSE),那么给一次机会给主窗口调用PreTranslateMessage(万一他是某个加速键消息呢?),这样能够确保在有非模式对话框的情况下还能确保主窗口的加速键好使。

      我做了一个小例子,在对话框类的PreTranslateMessage中,返回FALSE。在主窗口显示这个非模式对话框,在对话框拥有焦点的时候,仍然能够激活主窗口的快捷键。

 

      总之,整个框架就是让每个消息的目标窗口(包括他的父窗口)都有机会参和消息到来之前的处理。呵呵~

      至此,非对话框的消息循环和消息泵的机制就差不多了。这个机制在一个无限循环中,不断地从消息队列中获取消息,并且确保了程式的线程消息能够得到机会处理,窗口消息在预处理之后被发送到相应的窗口处理过程。那么,更有一点疑问,为什么要一会儿调用

::PeekMessage,一会儿调用::GetMessage呢,他们有什么区分?

     NOTE:一般来说,GetMessage被设计用来高效地从消息队列获取消息。假如队列中没有消息,那么函数GetMessage将导致线程休眠(让出CPU时间)。而PeekMessage是判断消息队列中假如没有消息,他马上返回0,不会导致线程处于睡眠状态。

     在上面的phase1第一个内循环中用到了PeekMessage,他的参数PM_NOREMOVE表示并不从消息队列中移走消息,而是个检测查询,假如消息队列中没有消息他立即返回0,假如这时线程空闲的话将会引起消息循环调用OnIdle处理过程(上面讲到了这个函数的重要性)。假如将::PeekMessage改成::GetMessage(***),那么假如消息队列中没有消息,线程将休眠,直到线程下一次获得CPU时间并且有消息出现才可能继续执行,这样,消息循环的空闲时间没有得到应用,OnIdle也将得不到执行。这就是为什么既要用::PeekMessage(查询),又要用::GetMessage(做实际的工作)的缘故。

      第二部分: 对话框程式的消息循环机制

      基于对话框的MFC工程和上面的消息循环机制不相同。实际上MFC的对话框工程程式就是模式对话框。他和上面讲到的非对话框程式的不同之处,主要在于应用程式对象的InitInstance()不相同。

[cpp:nogutter] view plain copy print ?
  1. //dlg_5Dlg.cpp   
  2. BOOL CDlg_5App::InitInstance()  
  3. {  
  4.     AfxEnableControlContainer();  
  5. #ifdef _AFXDLL   
  6.     Enable3dControls();   // Call this when using MFC in a shared DLL   
  7. #else   
  8.     Enable3dControlsStatic(); // Call this when linking to MFC statically   
  9. #endif   
  10.   
  11.     CDlg_5Dlg dlg; //定义一个对话框对象   
  12.      m_pMainWnd = &dlg;  
  13.     int nResponse = dlg.DoModal(); //对话框的消息循环在这里面开始   
  14.      if (nResponse == IDOK)  
  15.     {  
  16.        // TODO: Place code here to handle when the dialog is   
  17.        //  dismissed with OK   
  18.     }  
  19.     else if (nResponse == IDCANCEL)  
  20.     {  
  21.       // TODO: Place code here to handle when the dialog is   
  22.       //  dismissed with Cancel   
  23.     }  
  24.   
  25.     // Since the dialog has been closed, return FALSE so that we exit the   
  26.    //  application, rather than start the application's message pump.   
  27.    return FALSE;  
  28. }  
//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类中:

[cpp:nogutter] view plain copy print ?
  1. int CWnd::RunModalLoop(DWORD dwFlags)  
  2. {  
  3.    ASSERT(::IsWindow(m_hWnd)); // window must be created   
  4.    ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state   
  5.   
  6.    // for tracking the idle time state   
  7.    BOOL bIdle = TRUE;  
  8.    LONG lIdleCount = 0;  
  9.    BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);  
  10.    HWND hWndParent = ::GetParent(m_hWnd);  
  11.    m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);  
  12.    MSG* pMsg = &AfxGetThread()->m_msgCur;  
  13.   
  14.    // acquire and dispatch messages until the modal state is done   
  15.    for (;;)  
  16.    {  
  17.       ASSERT(ContinueModal());  
  18.   
  19.       // phase1: check to see if we can do idle work   
  20.       while (bIdle &&  
  21.             !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))  
  22.       {  
  23.          ASSERT(ContinueModal());  
  24.   
  25.          // show the dialog when the message queue goes idle   
  26.          if (bShowIdle)  
  27.          {  
  28.             ShowWindow(SW_SHOWNORMAL);  
  29.             UpdateWindow();  
  30.             bShowIdle = FALSE;  
  31.          }  
  32.   
  33.          // call OnIdle while in bIdle state   
  34.          if (!(dwFlags & MLF_NOIDLEMSG)   
  35.              && hWndParent != NULL && lIdleCount == 0)  
  36.          {  
  37.             // send WM_ENTERIDLE to the parent   
  38.             ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);  
  39.          }  
  40.          if ((dwFlags & MLF_NOKICKIDLE) ||  
  41.            !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))  
  42.          {  
  43.             // stop idle processing next time   
  44.             bIdle = FALSE;  
  45.          }  
  46.       }  
  47.   
  48.       // phase2: pump messages while available   
  49.       do  
  50.       {  
  51.          ASSERT(ContinueModal());  
  52.   
  53.          // pump message, but quit on WM_QUIT   
  54.          //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。   
  55.           if (!AfxGetThread()->PumpMessage())  
  56.          {  
  57.             AfxPostQuitMessage(0);  
  58.             return -1;  
  59.          }  
  60.   
  61.          // show the window when certain special messages rec'd   
  62.          if (bShowIdle &&  
  63.             (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))  
  64.          {  
  65.              ShowWindow(SW_SHOWNORMAL);  
  66.   
  67.              UpdateWindow();  
  68.              bShowIdle = FALSE;  
  69.          }  
  70.   
  71.          if (!ContinueModal())  
  72.             goto ExitModal;  
  73.   
  74.          // reset "no idle" state after pumping "normal" message   
  75.          if (AfxGetThread()->IsIdleMessage(pMsg))  
  76.          {  
  77.             bIdle = TRUE;  
  78.             lIdleCount = 0;  
  79.           }  
  80.   
  81.        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));  
  82.     } //无限循环   
  83.   
  84.     ExitModal:  
  85.       m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);  
  86.       return m_nModalResult;  
  87. }  
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; } 先说说怎么退出这个无限循环,在代码中:
[cpp:nogutter] view plain copy print ?
  1. if (!ContinueModal())  
  2.     goto ExitModal;  
  3. // 决定是否退出循环,消息循环函数返回也就是快要结束结束程式了。   
  4. BOOL CWnd::ContinueModal()  
  5. {  
  6.    return m_nFlags & WF_CONTINUEMODAL;  
  7. }  
if (!ContinueModal()) goto ExitModal; // 决定是否退出循环,消息循环函数返回也就是快要结束结束程式了。 BOOL CWnd::ContinueModal() { return m_nFlags & WF_CONTINUEMODAL; } NOTE: CWnd::ContinueModal()函数检查对话框是否继续模式。返回TRUE,表示现在是模式的;返回FALSE,表示对话框已不是模式(将要结束)。假如要结束对话框,在内部最终会调用函数CWnd::EndModalLoop,他取消m_nFlags的模式标志(消息循环中的ContinueModal函数将返回FALSE,消息循环将结束,程式将退出);然后激发消息循环读取消息。也就是说,结束模式对话框是个标志,改变这个标志就能够了。他的代码是:
[cpp:nogutter] view plain copy print ?
  1. //wincore.cpp   
  2. void CWnd::EndModalLoop(int nResult)  
  3. {  
  4.    ASSERT(::IsWindow(m_hWnd));  
  5.   
  6.    // this result will be returned from CWnd::RunModalLoop   
  7.    m_nModalResult = nResult;  
  8.   
  9.    // make sure a message goes through to exit the modal loop   
  10.    if (m_nFlags & WF_CONTINUEMODAL)  
  11.    {  
  12.      m_nFlags &= ~WF_CONTINUEMODAL;  
  13.      PostMessage(WM_NULL);  
  14.    }  
  15. }  
//wincore.cpp void CWnd::EndModalLoop(int nResult) { ASSERT(::IsWindow(m_hWnd)); // this result will be returned from CWnd::RunModalLoop m_nModalResult = nResult; // make sure a message goes through to exit the modal loop if (m_nFlags & WF_CONTINUEMODAL) { m_nFlags &= ~WF_CONTINUEMODAL; PostMessage(WM_NULL); } }

NOTE: PostMessage(NULL)是有用的。假如消息队列中没有消息的话,可能消息循环中的ContinueModal()不会马上执行,发送一个空消息是激发消息循环马上工作。

     下面说一下CWnd::RunModalLoop函数中的消息循环究竟干了些什么事情:
     1、第一个内循环。首先从消息队列中查询消息,假如对话框空闲,而且消息队列中没有消息,他做三件事情,大家应到都能从字面上明白什么意思。最重要的是发送WM_KICKIDLE消息。为什么呢?第一部分讲到了,非对话框程式用OnIdle来更新用户界面(UI),比如工具栏,状态栏。那么,假如对话框中也有工具栏和状态栏呢,在哪里更新(网上有很多这样的程式)?能够处理WM_KICKIDLE消息:

[cpp:nogutter] view plain copy print ?
  1. LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l)  
  2.  {  
  3.     //调用CWnd::UpdateDialogControls更新用户界面   
  4.     UpdateDialogControls(this, TRUE);  
  5.     return 0;  
  6.  }  
LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l) { //调用CWnd::UpdateDialogControls更新用户界面 UpdateDialogControls(this, TRUE); return 0; }

NOTE: CWnd::UpdateDialog函数发送CN_UPDATE_COMMAND_UI消息给任何的用户界面对话框控件。

     2、第二个内循环。最重要的还是PumpMessage派送消息到目标窗口。其他的,像第二个if语句,0x118消息似乎是WM_SYSTIMER消息(系统用来通知光标跳动的一个消息)。也就是说,假如消息为WM_SYSTIMER或WM_SYSKEYDOWN,并且空闲显示标志为真的话,就显示窗口并通知窗口立即重绘。

     总之,对话框的消息循环机制和非对话框(比如SDI,MDI)还是类似的,仅仅侧重点不同。模式对话框是模式显示,自然有他的特点。下面部分讨论一下模式对话框和非模式对话框的区分。因为模式对话框有自己的特别消息循环;而非模式对话框,共用程式的消息循环,和普通的窗口已没有什么大的区分了。

      第三部分:模式对话框和非模式对话框的区分

      这个话题已有很多人讨论,我说说我所理解的意思。
      在MFC框架中,一个对话框对象DoModal一下就能产生一个模式对话框,Create一下就能产生一个非模式对话框。实际上,无论是模式对话框还是非模式对话框,在MFC内部都是调用::CreateDialogIndirect(***)函数来创建非模式对话框。只是模式对话框作了更多的工作,包括使父窗口无效,然后进入自己的消息循环等等。::CreateDialogIndirect(***)函数最终调用CreateWindowEx函数通知系统创建窗体并返回句柄,他内部没有实现自己的消息循环。
非模式对话框创建之后立即返回,并且和主程式共用一个消息循环。非模式对话框要等对话框结束之后才返回,自己有消息循环。比如下面的代码:

[cpp:nogutter] view plain copy print ?
  1. CMyDlg* pdlg = new CMyDlg;  
  2. pdlg ->Create(IDD_DIALOG1);  
  3. pdlg->ShowWindow(SW_SHOW);  
  4. MessageBox("abc");  
CMyDlg* pdlg = new CMyDlg; pdlg ->Create(IDD_DIALOG1); pdlg->ShowWindow(SW_SHOW); MessageBox("abc");

     非模式对话框和消息框MessageBox几乎是同时弹出来。而假如将Create改成DoModal,那么,只能弹出模式对话框,在关闭了对话框之后(模式对话框自己的消息循环结束),消息框才弹出来。

     NOTE:能够在模式对话框中调用GetParent()->EnableWindow(true);这样,主窗口的菜单,工具栏又激活了,能用了。MFC使用非模式对话框来模拟模式对话框,而在win32 SDK程式中,模式对话框激发他的父窗口Enable操作是没有效果的。

     关于消息循环总结:

    1、我们站在一个什么高度看消息循环?消息循环其实没有什么深奥的道理。假如一个邮递员要不断在一个城市中送信,我们需要他做什么?需要他来回跑,但他一次只能在一个地方出现。假如我们的应用程式只有一个线程的话,我们要他不断地为窗口传递消息,我们怎么做?在一个循环中不断的检测消息,并将他发送到适当的窗口。窗口能够有很多个,但消息循环只有一个,而且每时每刻最多只有一个地方在执行代码。为什么? 看第二点。

    2、因为是单线程的(程式进程启动的时候,只有而且有一个线程,我们称他为主线程),所以就像邮递员相同,每次只能在某一个地方干活。什么意思呢?举个例子,用::DiapatchMessage派送消息,在窗口处理过程(WinProc,窗口函数)返回之前,他是阻塞的,不会立即返回,也就是消息循环此时不能再从消息队列中读取消息,直到::DispatchMessage返回。假如您在窗口函数中执行一个死循环操作,就算您用PostQuitMessage函数退出,程式也会down掉。

[cpp:nogutter] view plain copy print ?
  1. while(1)  
  2. {  
  3.     PostQuitMessage(0); //程式照样down.   
  4. }  
while(1) { PostQuitMessage(0); //程式照样down. }

     所以,当窗口函数处理没有返回的时候,消息循环是不会从消息队列中读取消息的。这也是为什么在模式对话框中要自己用无限循环来继续消息循环,因为这个无限循环阻塞了原来的消息循环,所以,在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取消息并派送消息了。要不然程式就不会响应了,这不是我们所希望的。

     所以说,消息循环放在程式的什么的地方都基本上是过的去的,比如放在DLL里面。但是,最好在任何时候,只有一个消息循环在工作(其他的都被阻塞了)。然后,我们要作好的一件事情,就是怎么从消息循环中退出!当然用WM_QUIT是能够拉~(PostThreadMessage也是个好主意),这个消息循环退出后,可能程式退出,也可能会激活另外一个被阻塞的消息循环,程式继续运行。这要看您怎么想,怎么去做。最后一个消息循环结束的时候,也许就是程式快结束的时候,因为主线程的执行代码也快要完了(除非BT的再作个死循环)。

 

     NOTE: 让windows系统知道创建一个线程的唯一方法是调用API CreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)。似乎windows核心编程说,在win2000下,系统用CreateRemoteThread函数来创建线程,CreateThread在内部调用CreateRemoteThread。但是这不是争论的焦点,至少win98下CreateRemoteThread并不能正常工作,还是CreateThread主持大局。

     3、在整个消息循环的机制中,还必须谈到窗口函数的可重入性。什么意思?就是窗口函数(他是个回调函数)的代码什么时候都能够被系统(调用者一般是user32模块)调用。比如在窗口过程中,向自己的窗口SendMessage(***);那么执行过程是怎样的?
      我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程式不就互相死锁了吗? 
      其实是不会的。windows设计一套适合SendMessage的算法,他判断假如发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入),并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子,我们调用SendMessage(***)发送消息到本窗口,那么窗口过程再次被调用,处理完消息之后将结果返回,然后SendMessage之后的程式接着执行。对于非队列消息,假如没有窗口重入,不知道会是什么样子。

     NOTE: 由于窗口的可重入性。在win32 SDK程式中应尽量少用全局变量和静态变量,因为在窗口函数执行过程中可能窗口重入,假如重入后将这些变量改了,但您的程式在窗口重入返回之后继续执行,可能就是使用已改变的全局或静态变量。在MFC中(任何窗口的窗口函数基本上都是AfxWndProc),按照类的思想进行了组织,一般变量都是类中的,好管理的多。

     4、MFC中窗口类(比如C**View,CFrameWnd等)中的MessageBox函数,连同AfxMessageBox函数都是阻塞原有的消息循环的。由消息框内部的一个消息循环来从消息队列中读取消息,并派送消息(和模式对话框类似)。实际上,这些消息函数最终调用的是::MessageBox,他在消息框内部实现了一个消息循环(原有的主程式消息循环被阻塞了)。论坛中碰到过几次关于计时器和消息框的问题,看下面的代码:

[cpp:nogutter] view plain copy print ?
  1. void CTest_recalclayoutView::OnTimer(UINT nIDEvent)   
  2. {  
  3.    // TODO: Add your message handler code here and/or call default   
  4.    MessageBox("abc");  
  5.    while(1); //设计一个死循环   
  6.    CView::OnTimer(nIDEvent);  
  7. }  
void CTest_recalclayoutView::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default MessageBox("abc"); while(1); //设计一个死循环 CView::OnTimer(nIDEvent); } 咱让OnTimer大约5秒钟弹出一个消息框。那么,消息框不断的被弹出来,只要消息框不被关闭,那么程式就不会进入死循环。实际上,每次弹出对话框,都是最上层的那个消息框掌控着消息循环,其他的消息循环被阻塞了。只要不关闭最上面的消息框,while(1);就得不到执行。假如点了关闭,程式就进入了死循环,只能用ctrl+alt+del来解决问题了。

     5、消息循环在很多地方都有应用。比如应用在线程池中。一个线程的执行周期一般在线程函数返回之后结束,那么怎么延长线程的生命周期呢?一种方法就是按照消息循环的思想,在线程中加入消息循环,不断地从线程队列读取消息,并处理消息,线程的生命周期就保持着直到这个消息循环的退出。

     NOTE:只要线程有界面元素或调用GetMessage,或有线程消息发送过来,系统就会为线程创建一个消息队列。

      6、在单线程程式中,假如要执行一个长时间的复杂操作而且界面要有相应的话,能够考虑用自己的消息泵。比如,能够将一个阻塞等待操作放在一个循环中,并将超时值配置得比较小,然后每个等待的片段中用消息泵继续消息循环,使界面能够响应用户操作。等等之类,都能够应用消息泵(调用一个类似这样的函数):

[cpp:nogutter] view plain copy print ?
  1. BOOL CChildView::PeekAndPump()  
  2. {  
  3.    MSG msg;  
  4.    while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))  
  5.    {  
  6.       if(!AfxGetApp()->PumpMessage())  
  7.       {  
  8.         ::PostQuitMessage(0);  
  9.         return false;  
  10.       }  
  11.    }   
  12.    return true;  
  13. }  
BOOL CChildView::PeekAndPump() { MSG msg; while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) { if(!AfxGetApp()->PumpMessage()) { ::PostQuitMessage(0); return false; } } return true; } 其实,用多线程也能解决复杂运算时的界面问题,但是没有这么方便,而且一般要加入线程通信和同步,考虑的事情更多一点。

      综上所述,MFC消息循环就那么回事,主要思想还是和SDK中差不多。这种思想主要的特点表现在迎合MFC整个框架上,为整个框架服务,为应用和功能服务。这是我的理解。呵呵~

你可能感兴趣的:(vc++)