執行緒 (轉)

gugu99發表於2007-08-16
執行緒 (轉)[@more@]

進入MFC講壇的前言(二)

-4-1 8:09:12  YESKY  毛守焱  閱讀次數: 2420 MFC的WinMain 

  使用MFC的員剛開始都會提出這樣一個問題:我的程式是從哪兒開始的?回答是:從WinMain()開始執行的。提出這樣的問題是由於在他們所編寫的MFC應用中看不到WinMain()。這個函式是隱藏在MFC中,MFC的設計者將它作得很通用(這主要得益於Window的訊息的程式設計機制,使得作一個通用的WinMain()很容易),因此在一般情況下,無需更改WinMain()的程式碼,MFC的設計者也不提倡程式設計師修改WinMain()的程式碼。在MFC中,實際實現WinMain()的程式碼是AfxWinMain()函式(根據其字首Afx就知道這是一個全域性的MFC函式)。

  一個應用程式(或程式)是由一個或多個併發的執行緒組成的,其中第一個啟動的執行緒稱為主執行緒,在Window下,一般將執行緒分成兩大類,介面執行緒和工作執行緒,工作執行緒就是一般的執行緒,它沒有視窗,沒有訊息佇列等,介面執行緒擁有一個或多個視窗,擁有一個訊息佇列和其他專屬於介面執行緒的元素。在討論AfxWinMain()之前,首先要簡略提一下MFC中的兩個重要的類,CWinThread和CWinApp,CWinThread是用來封裝介面執行緒的類,CWinApp是從CWinThread派生而來的。在CWinThread中,有兩個很重要的虛擬函式InitInstance()和ExitInistance(),MFC的程式設計師應該對這兩個函式應該很熟悉。在CWinApp中,增加了另外一個虛擬函式InitApplication(),討論AfxWinMain()的主要目的是看這些函式是如何被的。

  AfxWinMain()的程式碼如下:

  int AFX AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

  LPTSTR lpCmdLine, int nCmdShow)

  {

   ASSERT(hPrevInstance == NULL); file://在win32下,hPrevInstance始終為NULL

   int nReturnCode = -1;

   CWinThread* pThread = AfxGetThread();

   CWinApp* pApp = AfxGetApp();

   // AFX internal initialization

   if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))

    goto InitFailure;

    // App global initializations (rare)

   if (pApp != NULL && !pApp->InitApplication())

    goto InitFailure;

    // Perform specific initializations

   if (!pThread->InitInstance())

   {

    if (pThread->m_pMainWnd != NULL)

    {

     TRACE0("Warning: Destroying non-NULL m_pMainWnd ");

     pThread->m_pMainWnd->DestroyWindow();

    }

    nReturnCode = pThread->ExitInstance();

    goto InitFailure;

    }

   nReturnCode = pThread->Run();

   InitFailure:

   AfxWinTerm();

  return nReturnCode;

  }

  在上面的程式碼中,AfxGetThread()返回的是當前介面執行緒的指標,AfxGetApp()返回的是應用程式物件的指標,如果該應用程式(或程式)只有一個介面執行緒在執行,那麼這兩者返回的都是一個全域性的應用程式物件指標,這個全域性的應用程式物件就是MFC應用框架所預設的theApp物件(每次使用AppWizard生成一個SDI或MDI應用程式時,AppWizard都會新增CYourApp theApp這條語句,AfxGetApp()返回的就是這個theApp的地址)。

CWinApp::InitApplication(), CWinThread::InitInstance(), CWinThread::ExitInstance()是如何被呼叫的,從上面的程式碼一看就知,我不再贅述。下面我們把焦點放在CWinThread::Run()上。

MFC的控制中心――CWinThread::Run()

  說CWinThread::Run()是MFC的控制中心,一點也沒有誇大。在MFC中,所有來自於訊息佇列的訊息的分派都是在CWinThread::Run()函式中完成的,同AfxWinMain()一樣,這個函式也是對程式設計師是不可見的,其道理同AfxWinMain()的一樣。

  首先要提的一點是,對每條從訊息佇列取出來的訊息,MFC根據訊息的型別,按照某個特定的進行分發處理,這個分發模式是MFC自己定義的。固定的訊息分發流程和在這個流程中的可動態改變其行為的虛擬函式就構成了MFC的訊息分發模式。應用程式可以透過過載這些虛擬函式,來區域性定製自己的的訊息分發模式。正是透過這些虛擬函式,MFC為應用程式提供了足夠的靈活性。下面討論的所有程式碼都來自於MFC中的threadcore.cpp,它們都是CWinThread的成員。

  CWinThread::Run()的結構

  CWinThread::Run()的程式碼如下:

  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 pum "normal" message

      if (IsIdleMessage(&m_msgCur))

      {

       bIdle = TRUE;

       lIdleCount = 0;

      }

    } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));

   }

   ASSERT(FALSE); // not reachable

  }

CWinThread::Run()的處理過程如下:

  先根據空閒標誌以及訊息佇列是否為空這兩個條件判斷當前執行緒是否處於空閒狀態(這個“空閒”的含義同操作的含義不同,是MFC自己所謂的“空閒”),如果是,就呼叫CWinThread::OnIdle(),這也是我們比較熟悉的一個虛擬函式。

  如果不是,從訊息佇列中取出訊息,進行處理,直到訊息佇列為空。

  在這裡,我們發現,MFC不是呼叫GetMessage()從執行緒訊息佇列中取訊息,而是呼叫PeekMessage()。其原因在於,GetMessage()是一個具有同步行為的函式,如果訊息佇列中沒有訊息,GetMessage()會一直阻塞,使得執行緒處於睡眠狀態,直到訊息佇列中有一條或多條訊息,才會喚醒該執行緒,GetMessage()才會返回,如果執行緒處於睡眠狀態了,就不會使執行緒具有MFC所謂的“空閒”狀態了;而PeekMessage()則是一個具有非同步行為的函式,如果訊息佇列中沒有訊息,它馬上返回0,不會導致執行緒處於睡眠狀態。

  在上面的程式碼中,有兩個函式值得探討,一個是空閒處理函式OnIdle(),另外一個是訊息分發處理函式PumpMessage()。不要忽視CWinThread的OnIdle()函式,它作了很多有意義的事情。下面討論PumpMessage(),OnIdle()將在後面的章節裡討論。

  CWinThread::Run()的核心――CWinThread::PumpMessage()

  標題強調了PumpMessage()的重要性,Run()是MFC的控制中心,而PumpMessage()又是Run()的核心,所以從MFC的真正控制中心是PumpMessage()。PumpMessage()的程式碼極其簡單:

  BOOL CWinThread::PumpMessage()

  {

   ASSERT_VALID(this);

   if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))

    return FALSE;

   // process this message

   if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))

   {

    ::TranslateMessage(&m_msgCur);

    ::DispatchMessage(&m_msgCur);

   }

   return TRUE;

  }

  首先,PumpMessage()呼叫GetMessage()從訊息佇列中取一條訊息,由於PumpMessage()是在訊息佇列中有訊息的時候才被呼叫的,所以GetMessage()會馬上返回,根據其返回值,判斷當前取出的訊息是不是WM_QUIT訊息(這個訊息一般對是透過呼叫PostQuitMessage()放入執行緒訊息佇列的),如果是,就返回FALSE,CWinThread::Run()該退出了,CWinThread::Run()直接呼叫CWinThread::ExitInstance()退出應用程式。在GetMessage()的後面是我們所熟悉的TranslateMessage()和DispatchMessage()函式。

可以看出,是否呼叫TranslateMessage()和DispatchMessage()是由一個名稱為PreTranslateMessage()函式的返回值決定的,如果該函式返回TRUE,則不會把該訊息分發給視窗函式處理。

  就我個人觀點而言,正是有了這個PreTranslateMessage(),才使得MFC能夠靈活的控制訊息的分發模式,可以說,PreTranslateMessage()就是MFC的訊息分發模式。

MFC的特色――PreTranslateMessage()

  經過層層扒皮,終於找到了CWinThread::Run()最具特色的地方,這就是PreTranslateMessage()函式。同前面使用SDK編寫的顯示”Hello, world!”程式的訊息迴圈不同的地方在於,MFC多了這個PreTranslateMessage(),PreTranslateMessage()最先獲得了應用程式的訊息處理權!下面我們對PreTranslateMessage()進行剝皮式分析。同前面一樣,首先看看實際的PreTranslateMessage()的程式碼:

  BOOL CWinThread::PreTranslateMessage(MSG* pMsg)

  {

   ASSERT_VALID(this);

   // if this is a thread-message, short-circuit this function

   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

  }

  PreTranslateMessage()的處理過程如下:

  首先判斷該訊息是否是一個執行緒訊息(訊息的視窗控制程式碼為空的訊息),如果是,交給DispatchThreadMessageEx()處理。我們暫時不管DispatchThreadMessageEx(),它不是我們討論的重點。

  呼叫CWnd::WalkPreTranslateTree()對該訊息進行處理,注意該函式的一個引數是執行緒主視窗的控制程式碼,這是PreTranslateMessage()的核心程式碼,在後面會對這個函式進行詳細的分析。

  對於非模式對話方塊,這特別的、額外的處理。

  下面詳細討論一下CWnd::WalkPreTranslateTree()函式,它的程式碼很簡單:

  BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)

  {

   ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));

   ASSERT(pMsg != NULL);

   // walk from the target window up to the hWndStop window checking

   // if any window wants to translate this message

   for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))

   {

    CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

    if (pWnd != NULL)

    {

     // target window is a C++ window

     if (pWnd->PreTranslateMessage(pMsg))

      return TRUE; // trapped by target window (eg: accelerators)

    }

    // got to hWndStop window without interest

    if (hWnd == hWndStop)

    break;

   }

   return FALSE; // no special processing

  }

CWnd::WalkPreTranslateTree()的所使用的策略很簡單,擁有該訊息的視窗最先獲得該訊息的處理權,如果它不想對該訊息進行處理(該視窗物件的PreTranslateMessage()函式返回FALSE),就將處理權交給它的父親視窗,如此向樹的根部遍歷,直到遇到hWndStop(在CWinThread::PreTranslateMessage()中,hWndStop表示的是執行緒主視窗的控制程式碼)。記住這個訊息處理權的傳遞方向,是由樹的某個一般節點或葉子節點向樹的根部傳遞!

  小結:

  下面對這一章作一個小結。

  MFC訊息控制流最具特色的地方是CWnd類的虛擬函式PreTranslateMessage(),透過過載這個函式,我們可以改變MFC的訊息控制流程,甚至可以作一個全新的控制流出來,在下面的一章會對MFC的實現作詳細介紹。

  只有穿過訊息佇列的訊息才受PreTranslateMessage()影響,採用SendMessage()或其他類似的方式向視窗直接傳送的而不經過訊息佇列的訊息根本不會理睬PreTranslateMessage()的存在

  傳給PreTranslateMessage()的訊息是未經翻譯過的訊息,它沒有經過TranslateMessage()處理,在某些情況下,要仔細處理,以免漏掉訊息。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-960758/,如需轉載,請註明出處,否則將追究法律責任。

相關文章