深入理解MFC消息循環

首先,應該清楚MFC的消息循環(::GetMessage,::PeekMessage),消息泵(CWinThread:: PumpMessage)MFC的消息在窗口之間的路由是兩件不同的事情。在MFC的應用程序中(應用程序類基於CWinThread繼承),必須要有 一個消息循環,他的作用是從應用程序的消息隊列中讀取消息,並把它派送出去(::DispatchMessage)。而消息路由是指消息派送出去之後,系 統(USER32.DLL)把消息投遞到哪個窗口,以及以後消息在窗口之間的傳遞是怎樣的。

    消息分爲隊列消息(進入線程的消息隊列)和非隊列消息(不進入線程的消息隊列)。對於隊列消息,最常見的是鼠標和鍵盤觸發的消息,例如 WM_MOUSERMOVE,WM_CHAR等消息;還有例如:WM_PAINTWM_TIMERWM_QUIT。當鼠標、鍵盤事件被觸發後,相應的 鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,然後輸送到系統消息隊列,由Windows系統負責把消息加入到相應線程的消息隊列中,於是就有了消 息循環(從消息隊列中讀取並派送消息)。還有一種是非隊列消息,他繞過系統隊列和消息隊列,直接將消息發送到窗口過程。例如,當用戶激活一個窗口系統發送 WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。創建窗口時發送WM_CREATE消息。在後面你將看到,MS這麼設計是很有道理的,以及他的整套實現機制。

  這裏講述MFC的消息循環,消息泵。先看看程序啓動時,怎麼進入消息循環的:

_tWinMain ->AfxWinMain ->AfxWinInit ->CWinThread::InitApplication ->CWinThread::InitInstance ->CWinThread::Run

  非對話框程序的消息循環的事情都從這CWinThread的一Run開始...

  第一部分:非對話框程序的消息循環機制。

//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消息:

if (!PumpMessage())
    return ExitInstance();

   PumpMessage中,如果收到WM_QUIT消息,那麼返回FALSE,所以ExitInstance()函數執行,跳出循環,返回程序的退出代碼。所以,一個程序要退出,只用在代碼中調用函數

  VOID PostQuitMessage( int nExitCode )。指定退出代碼nExitCode就可以退出程序。

  下面討論一下這個函數Run的流程,分兩步:

  1,第一個內循環phase1bIdle代表程序是否空閒。他的意思就是,如果程序是空閒並且消息隊列中沒有要處理的消息,那麼調用虛函數OnIdle 進行空閒處理。在這個處理中將更新UI界面(比如工具欄按鈕的enabledisable狀態),刪除臨時對象(比如用FromHandle得到的對象 指針。由於這個原因,在函數之間傳遞由FromHandle得到的對象指針是不安全的,因爲他沒有持久性)OnIdle是可以重載的,你可以重載他並返 回TRUE使消息循環繼續處於空閒狀態。

  NOTEMS用臨時對象是出於效率上的考慮,使內存有效利用,並能夠在空閒時自動撤銷資源。關於由句柄轉換成對象,可以有若干種方法。一般是先申明一個 對象obj,然後使用obj.Attatch來和一個句柄綁定。這樣產生的對象是永久的,你必須用obj.Detach來釋放對象。

  2,第二個內循環phase2。在這個循環內先啓動消息泵(PumpMessage),如果不是WM_QUIT消息,消息泵將消息發送出去(::DispatchMessage)。消息的目的地是消息結構中的hwnd字段所對應的窗口。

//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類或者主窗口類 中,那麼,它是怎麼進入別的類的呢?代碼如下:

//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,CMDIFrameWndCMDIChildWnd等窗口類有。所以,第三步的意思 是,如果消息的目標窗口(他的父窗口不是主窗口,比如一個這樣的非模式對話框)使消息的預處理繼續漫遊的話(他的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()不一樣。

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

}

先說說怎麼退出這個無限循環,在代碼中:

if (!ContinueModal())

           goto ExitModal;

// 決定是否退出循環,消息循環函數返回也就是快要結束結束程序了。

BOOL CWnd::ContinueModal()

{

           return m_nFlags & WF_CONTINUEMODAL;

}

  NOTE: CWnd::ContinueModal()函數檢查對話框是否繼續模式。返回TRUE,表示現在是模式的;返回FALSE,表示對話框已經不是模式(將要結束)

  如果要結束對話框,在內部最終會調用函數CWnd::EndModalLoop,它取消m_nFlags的模式標誌(消息循環中的 ContinueModal函數將返回FALSE,消息循環將結束,程序將退出);然後激發消息循環讀取消息。也就是說,結束模式對話框是一個標誌,改變 這個標誌就可以了。他的代碼是:

//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消息:

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函數通知系統創建窗體並返回句柄,他內部沒有實 現自己的消息循環。
非模式對話框創建之後立即返回,並且和主程序共用一個消息循環。非模式對話框要等對話框結束之後才返回,自己有消息循環。比如下面的代碼:

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掉。

while(1)
{
    PostQuitMessage(0); //
程序照樣down.
}

  所以,當窗口函數處理沒有返回的時候,消息循環是不會從消息隊列中讀取消息的。這也是爲什麼在模式對話框中要自己用無限循環來繼續消息循環,因爲這個無限 循環阻塞了原來的消息循環,所以,在這個無限循環中要用GetMessage,PeekMessage,DispatchMessage來從消息隊列中讀 取消息並派送消息了。要不然程序就不會響應了,這不是我們所希望的。

  所以說,消息循環放在程序的什麼的地方都基本上是過的去的,比如放在DLL裏面。但是,最好在任何時候,只有一個消息循環在工作(其他的都被阻塞了)。然 後,我們要作好的一件事情,就是怎麼從消息循環中退出!當然用WM_QUIT是可以拉~(PostThreadMessage也是個好主意),這個消息循 環退出後,可能程序退出,也可能會激活另外一個被阻塞的消息循環,程序繼續運行。這要看你怎麼想,怎麼去做。最後一個消息循環結束的時候,也許就是程序快 結束的時候,因爲主線程的執行代碼也快要完了(除非BT的再作個死循環)

  NOTE: windows系統知道創建一個線程的唯一方法是調用API CreatThread函數(__beginthreadex之類的都要在內部調用他創建新線程)。好像windows核心編程說,在win2000下, 系統用CreateRemoteThread函數來創建線程,CreateThread在內部調用CreateRemoteThread。不過這不是爭論 的焦點,至少win98CreateRemoteThread並不能正常工作,還是CreateThread主持大局。

  3,在整個消息循環的機制中,還必須談到窗口函數的可重入性。什麼意思?就是窗口函數(他是個回調函數)的代碼什麼時候都可以被系統(調用者一般是user32模塊)調用。比如在窗口過程中,向自己的窗口SendMessage(***);那麼執行過程是怎樣的?

 
我們知道,SendMessage是要等到消息發送並被目標窗口執行完之後才返回的。那麼窗口在處理消息,然後又等待剛纔發送到本窗口的消息被處理後之後(SendMessage返回)才繼續往下執行,程序不就互相死鎖了嗎?

 
其實是不會的。windows設計一套適合SendMessage的算法,他判斷如果發送的消息是屬於本線程創建的窗口的,那麼直接由user32模塊調 用窗口函數(可能就有窗口重入),並將消息的處理結果結果返回。這樣做體現了窗口重入。上面的例子,我們調用SendMessage(***)發送消息到 本窗口,那麼窗口過程再次被調用,處理完消息之後將結果返回,然後SendMessage之後的程序接着執行。對於非隊列消息,如果沒有窗口重入,不知道 會是什麼樣子。

  NOTE: 由於窗口的可重入性。在win32 SDK程序中應儘量少用全局變量和靜態變量,因爲在窗口函數執行過程中可能窗口重入,如果重入後將這些變量改了,但你的程序在窗口重入返回之後繼續執行, 可能就是使用已經改變的全局或靜態變量。在MFC(所有窗口的窗口函數基本上都是AfxWndProc),按照類的思想進行了組織,一般變量都是類中 的,好管理的多。

  4,MFC中窗口類(比如C**View,CFrameWnd)中的MessageBox函數,以及AfxMessageBox函數都是阻塞原有的消息 循環的。由消息框內部的一個消息循環來從消息隊列中讀取消息,並派送消息(和模式對話框類似)。實際上,這些消息函數最終調用的是:: MessageBox,它在消息框內部實現了一個消息循環(原有的主程序消息循環被阻塞了)。論壇中碰到過幾次關於計時器和消息框的問題,看下面的代碼:

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,在單線程程序中,如果要執行一個長時間的複雜操作而且界面要有相應的話,可以考慮用自己的消息泵。比如,可以將一個阻塞等待操作放在一個循環中,並將 超時值設置得比較小,然後每個等待的片段中用消息泵繼續消息循環,使界面能夠響應用戶操作。等等之類,都可以應用消息泵(調用一個類似這樣的函數)

BOOL CChildView::PeekAndPump()

{

           MSG msg;

           while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))

           {

                if(!AfxGetApp()->PumpMessage())

                {

                      ::PostQuitMessage(0);

                      return false;

                }

           }

           return true;

}

   其實,用多線程也能解決複雜運算時的界面問題,但是沒有這麼方便,而且一般要加入線程通信和同步,考慮的事情更多一點。 

發佈了19 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章