天天看點

深入剖析MFC中Windows消息處理機制

本人對Windows系統、MFC談不上有深入的了解,但對MFC本身包裝API的機制很有興趣,特别是讀了候老師的《深入淺出MFC》後,感覺到Visual C++的Application FrameWork十分精制。在以前,我對SDI結構處理消息有一定的認識,但對于模式對話框的消息機制不了解,讀了《深入》一書也沒能得到解決,近日,通過在網友的幫助和查閱MSDN,自認為已經了解。一時興起,寫下這些文字,沒有其它目的,隻是希望讓後來者少走彎路,也希望和我一樣喜歡“鑽牛角尖”的人共同讨論、學習。如果你是牛人,那麼你現在要慎重考慮有沒有充足的時間讀這些幼稚文字。

  正文:

  Windows程式和DOS程式的主要不同點之一是:Windows程式是以事件為驅動、消息機制為基礎。如何了解?

  舉了例子,當你CLICK Windows “開始”BUTTON時,為什麼就會彈出一個菜單呢?

  當你單擊滑鼠左鍵時,作業系統中與MOUSE相關的驅動程式在第一時間内得到這個信号[LBUTTONDOWN],然後它通知作業系統―――“嗨,滑鼠左鍵被單擊了!”,作業系統得到這一信号後,馬上要判斷――使用者單擊滑鼠左鍵,這是針對哪個視窗呢?如何判斷?這很簡單!目前狀态中,具有焦點的視窗[或控件]就是了[這裡當然是“開始”BUTTON了]。然後作業系統馬上向這個視窗發送一條消息到這個視窗所在程序的消息隊列,消息内容應是消息本身的代号、附加參數、視窗句柄…等等了。那麼,隻有作業系統才有資格發送消息至某一視窗的消息隊列嗎?不然,其它程式也有資格。你可以在你的程式中調用:SendMessage 、PostMessage。這樣,被單擊的視窗得到了一條由作業系統發送的包含CLICK的消息,作業系統已經暫時不再管視窗的任何事,因為它還要忙于處理其它事務。你的程式得到一條消息後如何做呢?Windows對于你在“開始”BUTTON上的單擊事件做出如下反映:彈出一菜單。可是,得到消息到做出反映這一過程是如何實作的呢?這就是本文讨論的主要内容[當然隻是針對MFC了]。

  我首先簡要談一下SDI,然後會花更多文字描述模式對話框。

  對于SDI視窗,你的應用程式類的InitInstance()大約如下:

BOOL CEx06aApp::InitInstance()

{

  ……………

 CSingleDocTemplate* pDocTemplate;

 pDocTemplate = new CSingleDocTemplate(

  IDR_MAINFRAME,

  RUNTIME_CLASS(CEx06aDoc),

  RUNTIME_CLASS(CMainFrame), // main SDI frame window

  RUNTIME_CLASS(CEx06aView));

 AddDocTemplate(pDocTemplate);

 CCommandLineInfo cmdInfo;

 ParseCommandLine(cmdInfo);

 if (!ProcessShellCommand(cmdInfo))

  return FALSE;

 m_pMainWnd->ShowWindow(SW_SHOW);

 m_pMainWnd->UpdateWindow();

 return TRUE;

}

  完成一些如動态生成相關文檔、視,顯示主架構視窗、處理參數行資訊等工作。這些都是顯示在你工程中的“明碼”。我們現在把斷點設定到return TRUE;一句,跟入MFC源碼中,看看到底MFC内部做了什麼。

  程式進入SRC/WinMain.cpp,下一個大動作應是:

nReturnCode = pThread->Run();

  注意了,重點來了。F11進入

int CWinApp::Run()

 if (m_pMainWnd == NULL && AfxOleGetUserCtrl())

 {

  // Not launched /Embedding or /Automation, but has no main window!

  TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application./n");

  AfxPostQuitMessage(0);

 }

 return CWinThread::Run();

  再次F11進入:

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

 ASSERT(FALSE); // not reachable

BOOL CWinThread::IsIdleMessage(MSG* pMsg)

 // Return FALSE if the message just dispatched should _not_

 // cause OnIdle to be run. Messages which do not usually

 // affect the state of the user interface and happen very

 // often are checked for.

 // redundant WM_MOUSEMOVE and WM_NCMOUSEMOVE

 if (pMsg->message == WM_MOUSEMOVE || pMsg->message == WM_NCMOUSEMOVE)

  // mouse move at same position as last mouse move?

  if (m_ptCursorLast == pMsg->pt && pMsg->message == m_nMsgLast)

   return FALSE;

  m_ptCursorLast = pMsg->pt; // remember for next time

  m_nMsgLast = pMsg->message;

  return TRUE;

 // WM_PAINT and WM_SYSTIMER (caret blink)

 return pMsg->message != WM_PAINT && pMsg->message != 0x0118;

  這是SDI處理消息的中心機構,但請注意,它覺對不是核心!

  分析一下,在無限循環FOR内部又出現一個WHILE循環

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

  這段代碼是當你程式程序的消息隊列中沒有消息時,會調用OnIdle做一些後備工作,臨時對象在這裡被删除。當然它是虛函數。其中的PeekMessage,是檢視消息隊列,如果有消息傳回TRUE,如果沒有消息傳回FALSE,這裡指定PM_NOREMOVE,是指檢視過後不移走消息隊列中剛剛被檢視到的消息,也就是說這裡的PeekMessage隻起到一個檢測作用,顯然傳回FALSE時[即沒有消息],才會進入循環内部,執行OnIdle,當然了,你的OnIdle傳回FLASE,會讓程式不再執行OnIdle。