<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    深入剖析MFC中對于Windows消息處理、運行機制

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    序:

           本人對Windows系統、MFC談不上有深入的了解,但對MFC本身包裝API的機制很有興趣,特別是讀了候老師的《深入淺出MFC》后,感覺到VISUAL C++的Application FrameWork十分精制[不敢用“完美”一詞]。在以前,我對SDI結構處理消息有一定的認識,但對于模式對話框的消息機制不了解,讀了《深入》一書也沒能得到解決,近日,通過在CSDN上網友的幫助,和查閱MSDN,自認為已經了解。一時興起,寫下這些文字,沒有其它目的,只是希望讓后來者少走彎路,也希望和我一樣喜歡“鉆牛角尖”的人共同討論、學習。如果你是牛人,那么你現在要慎重考慮有沒有充足的時間讀這些幼稚文字[何出此言?請大家看一下:http://www.csdn.net/expert/topic/13/13451.xml的相關評論]。本文中有些“理論”是我自己胡亂猜測,還請大家指正.[文中內容有些不可避免的會和《深入》一書某些內容重復]。

    正文:

    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。你可能要問:
    當bidle=0或消息隊例中有消息時,程序又執行到哪了呢? 
    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));

    看啊,又進入一個循環!
    其中有個重要的函數,PumpMessage,內容如下:
    BOOL CWinThread::PumpMessage()
    {
     ASSERT_VALID(this);

     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;
    }
    如你所想,這才是MFC消息處理的核心基地[也是我個人認為的]。
    GetMessage不同于PeekMessae,它是不得到消息不罷體,PeekMessage如果發現消息隊列中沒有消息會返回0,而GetMessage如果發現沒有消息,等,直到有了消息,而且,GetMessage不同于PeekMessage,它會將消息移走[當然,PeekMessage也可以做到這點]。我想當你讀了這個函數后,你應明白PreTranslateMessage函數的用法了吧[我比較喜歡在程序中充分利用這個函數]。
    ::TranslateMessage(&m_msgCur);
    ::DispatchMessage(&m_msgCur);
    將消息發送到窗口的處理函數[它是由窗口類指定的],之后的動作一直到你的程序做出反映的過程,你可以在《深入》一書中得到完美的解釋。我們還是通過reurn TRUE;回到CWinThread::Run()中的Do{}while;循環。然后還是對IDLE的處理,即便剛才你的ONIDLE返回了FALSE,在這里你看到,你的程序還是有機會執行它的。然后又是利用PeekMessage檢測消息隊列:
    如果有消息[這個消息不被移動的原因是因為它要為PumpMessage內的GetMessage所利用。]再次進入PumpMessage[叫它“消息泵”吧]。
    如果沒有消息,退出DO循環,但它還在FOR內部,所以又執行第一個While循環。
    這是CwinThread::Run的一個執行過程。
    不用擔心退不出for(;;)如果你的消息隊列中有一條WM_QUIT,會使GetMessage返回0,然后PumpMessage返回0而RUN()內部:
    if (!PumpMessage())
     return ExitInstance();。

    SDI就說到這,下面我來談一下模式對話框。我分2種情況討論:
    一當你的工程以模式對話框為基礎時[沒父窗口,或為桌面]。
    與SDI不同處在于,在應用程序類的InItInstance內部:
    BOOL CComboBoxApp::InitInstance()
    {
     AfxEnableControlContainer();
     // Standard initialization
     // If you are not using these features and wish to reduce the size
     //  of your final executable, you should remove from the following
     //  the specific initialization routines you do not need.
    #ifdef _AFXDLL
     Enable3dControls();   // Call this when using MFC in a shared DLL
    #else
     Enable3dControlsStatic(); // Call this when linking to MFC statically
    #endif
     this->m_nCmdShow = SW_HIDE;
     CComboBoxDlg 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;
    }

    int nResponse = dlg.DoModal();一句使你的整個程序都在DoModal()內部進行。而且,你退出DoMal()時[你一定結束了你的對話框],InitInstance返回的是False,我們知道,這樣,CwinThread::Run是不會執行的。
    但對話框程序是在哪里進行消息處理的呢。
    原來,dlg.DoModal()內部會調用CwinThread::RunModalLoop,它起到的作用和RUN()是一樣的[當然內部有細小差別,請參考MSDN]!!!

    第二種情況,你是在SDI[或其它]程序中調用Dlg.DoModal() 產生了一模式對話框[有父窗口].

    這又是如何運作的呢?
    建了這樣一個工程做為例子。
    SDI,在View中處理LBUTTONDOWN:
    MyDLg.DoModal();
    MyDLg內有按鈕,以憊后用.

    沒有顯示模式對話框前,消息處理一直在Cthread::Run()中進行.
    你單擊后,程序執行點進入DoModal()內部的RunModalLoop,這又是一個消息處理機制.
    不過DoModal()中調用RunModalLoop,前會Disable掉它的父窗口.從RunModalLoop中出來后,再 Enable它.
    模式對話框和非模式對話框都是通過調用CreateDialogIndirect()產生創建對話框.那它和非模式對話框區別是什么造成的呢?
    1 模式對話框將父窗口DISABLE掉.
    我原以為被Disable的窗口是不接收消息的.但后來我馬上發現我是錯的.但,為什么你對被Disable的窗口進行KeyBorad,Mouse動作時,窗口沒反映呢,我想,這可能是操作系統從中搞的鬼.我在本文一開始,就寫出操作系統向窗口發送消息的過程,我想當它發現目標窗口處理Disabled狀態時,不會將消息發送給它,但這不能說窗口不接收消息,其它程序[或它本身]發送給它的消息還是可以接收并處理的.
    2 模式對話框本身有消息處理機制 RunModalLoop.

    對以上兩點加以實驗.
    我在我的剛才建的項目中的模式對話框中加上一個BUTTON,其中加入如下代碼:
    OnButton1()
    {
     GetParaent()->EnableWindow(1);
    }
    單擊,后我們發現,此時它已經不再表現為”模態”,我試著點擊菜單,還是會作出正常反映.
    我想這此消息[對于父窗口的,如:菜單動作]的處理也應是在模式對話框中的RunModalLoop中進行處理的吧[這點我不能確定].

     

    先寫到這里吧,一點濁見.請大家批評.
            

     

    CSDN烤雞翅膀
    2002-4-24

     

    from:http://blog.csdn.net/mahongxi/article/details/6353

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全