Thinking in MFC---消息機制1

Thinking in MFC---消息機制1

引言

在使用較長時間的MFC之後,感覺自己需要將零散的MFC知識整合一下,所以開始推出這個系列的博文,首先就從MFC經典的消息機制入手,來介紹MFC是怎麼運作的。這篇主要介紹一下消息機制中幾個基礎概念。

這篇主要介紹消息如何路由到主窗口。

一、WinMain函數

寫過win32程序,肯定只要我們要展示一個窗口,需要這個入口函數,這個就像控制檯程序中main函數,是程序的入口。但是在MFC項目創建之後,我們在項目工程卻找不到這個函數。

其實MFC已經將這個函數內部寫好了,具體在下面這個文件中。

Microsoft Visual Studio10.0\VC\atlmfc\src\mfc\winmain.cpp

//

以下winmain函數中的內容,這些代碼中的大部分我們暫時不做詳細介紹,在之後的篇章中會來討論這些代碼。

 
ASSERT(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)
        {
            TRACE(traceAppMsg,0, "Warning: Destroying non-NULLm_pMainWnd\n");
            pThread->m_pMainWnd->DestroyWindow();
        }
        nReturnCode= pThread->ExitInstance();
        goto InitFailure;
    }
    nReturnCode =pThread->Run();
 
InitFailure:
#ifdef _DEBUG
    // Check for missing AfxLockTempMap calls
    if (AfxGetModuleThreadState()->m_nTempMapLock !=0)
    {
        TRACE(traceAppMsg,0, "Warning: Temp map lock count non-zero(%ld).\n",
            AfxGetModuleThreadState()->m_nTempMapLock);
    }
    AfxLockTempMaps();
    AfxUnlockTempMaps(-1);
#endif
 
    AfxWinTerm();
    return nReturnCode;

上訴代碼中第27行代碼,我們需要找的消息循環就在Run函數中,接下來就看一下這個Run函數。

二、消息路由到主窗口

這個類微軟用來封裝MFC的線程。

這裏我們主要來討論這個類中的run函數

virtual int CWinThread::Run()
{
    ASSERT_VALID(this);
    BOOL bIdle =TRUE;
    LONGlIdleCount = 0;
    //這裏就是消息循環
    for (;;)
    {
         // 如果線程處於空閒狀態,消息隊列中也沒有消息,做空閒處理
        while(bIdle && !::PeekMessage(&m_msgCur,NULL, NULL, NULL, PM_NOREMOVE))
        {
            // PeekMessage()用於檢查消息隊列是否爲空,但並不處理消息
            if (!OnIdle(lIdleCount++))
                bIdle= FALSE;
        }
        do// 空閒處理接觸,進行消息處理
        {
            if (!PumpMessage())// 消息處理,如果是WM_QUIT就退出消息循環
                return ExitInstance();
            if (IsIdleMessage(&m_msgCur))
            {
                bIdle= TRUE;
                lIdleCount= 0;
            }
        } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE));
    }
    ASSERT(FALSE);//
}

這裏先說明,以上代碼包括下面的代碼都是筆者從網上找到的,所以對於其準確性不能夠保證,但是作爲介紹性說明,應該是沒有問題的。

我們按照函數執行順序往下看,首先我們發現了一個死循環,這個死循環便是我們要的消息循環。

接下來我們便是idle狀態的處理,沒有消息處理的時候,線程進入idle狀態。其中PeekMessage,便是用來判斷消息隊列中是否有消息。

再往下看,我們會發現一個叫PumpMessage,這個函數便是這裏的一個關鍵函數(從run函數的代碼中看,好像也沒有其對隊消息進行處理的函數了)。

接下來就看一下PumpMessage函數的源碼

BOOL CWinThread::PumpMessage()
{
    ASSERT_VALID(this);
    if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
        return FALSE;
    if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
    {
        ::TranslateMessage(&m_msgCur);
        ::DispatchMessage(&m_msgCur);
    }
    return TRUE;
}

按照執行順序看,我們會注意到GetMessage,這個函數這裏暫時不做介紹,就是從線程的消息隊列中獲取消息,並把消息內容放入一個結構的對象中(這裏就放入msgCur)。

接下來便是判斷消息是否是idle消息,如果不是就交給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 throughmain
    //   window'saccelerator table
    if (pMainWnd != NULL)
    {
        CWnd*pWnd = CWnd::FromHandle(pMsg->hwnd);
        if (pWnd->GetTopLevelParent() != pMainWnd)
            return pMainWnd->PreTranslateMessage(pMsg);
    }
 
    return FALSE;  // no special processing
}

我們按照函數的執行順序來看,首先判斷一下這個消息是否是發給窗口的。如果不是,那麼就是一個線程消息,就交給DispatchThreadMessageEx(這裏也暫時不介紹這個函數,不是這篇討論的重點)。

接下面,我們獲得主窗口對象,就把消息交給WalkPreTranslateTree處理。

繼續探討,我們有需要找到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 windowchecking
    // 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 bytarget window (eg: accelerators)
        }
        // got to hWndStop window without interest
        if (hWnd == hWndStop)
             break;
    }
    return FALSE; // no specialprocessing
}

略去一些空值的檢查,我們看到消息最後是交給了主窗口的PreTranslateMessage函數處理了。接下來就交給主窗口進行消息處理了。

這裏我們就大致瞭解了一個消息怎麼路由到主窗口的流程。

三、總結

開始編程不久,便接觸到了MFC。用了MFC寫了自己第一個視窗程序,寫了第一個遊戲,在學校課題演示的時候也是喜歡用MFC做演示。

現在工作了,項目的界面部分也在使用MFC開發的,值得慶幸的是,現在微軟對於MFC的界面做了很大的改善,想起剛開始不斷重寫控件類,那個真是記憶猶新。

在平時編程的時候,VS這個集成工具確實很方便。但現在想把一些MFC零零碎碎的知識整合起來。所以這個系列可能會深入地去介紹MFC。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章