091019(星期一)MFC消息循环2,分解RunModalLoop

来源:互联网 发布:局域网团队协作软件 编辑:程序博客网 时间:2024/05/20 14:25

 

CWnd::RunModalLoop(unsigned long 4) line 3438

CDialog::DoModal() line 539 + 12 bytes

CIntelligentFtpApp::InitInstance() line 65 + 11 bytes

AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f26, int 1) line 39 + 11 bytes

WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f26, int 1) line 30

WinMainCRTStartup() line 198 + 54 bytes

 

CIntelligentFtpApp恒返回FALSE

BOOL CIntelligentFtpApp::InitInstance()

{

……

       // Since the dialog has been closed, return FALSE so that we exit the

       //  application, rather than start the application's message pump.

       return FALSE;

}

这样就避免在AfxWinMain中继续调用nReturnCode = pThread->Run();

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

       LPTSTR lpCmdLine, int nCmdShow)

{……

       // Perform specific initializations

       if (!pThread->InitInstance())

       {

              if (pThread->m_pMainWnd != NULL)

              {

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

                     pThread->m_pMainWnd->DestroyWindow();

              }

              nReturnCode = pThread->ExitInstance();

              goto InitFailure;

       }

       nReturnCode = pThread->Run();

 

InitFailure:

       AfxWinTerm();

       return nReturnCode;

}

 

RunModalLoop的代码:

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

// 第一个循环:处理空闲工作,更新界面,状态栏,工具栏,发生WM_KICKIDLE消息,用于扩充处理。

              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;

                     }

 

/* 消息循环的退出机制:

决定是否退出循环,消息循环函数返回也就是快要结束结束程序了

BOOL CWnd::ContinueModal()

{

       return m_nFlags & WF_CONTINUEMODAL;

}

结束对话框,在内部最终会调用函数CWnd::EndModalLoop,它取消m_nFlags的模式标志(消息循环中的 ContinueModal函数将返回FALSE,消息循环将结束,程序将退出);然后激发消息循环读取消息。也就是说,结束模式对话框是一个标志,改变 这个标志就可以了。他的代码是wincore.cpp

void CWnd::EndModalLoop(int nResult)

{

           if (m_nFlags & WF_CONTINUEMODAL)

           {

                m_nFlags &= ~WF_CONTINUEMODAL;

                PostMessage(WM_NULL);

           }

}

*/

                     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;

}

 

函数PumpMessageApp的消息队列中取出消息并分发给对应的窗口类处理。

BOOL CWinThread::PumpMessage()

{

       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;

}

 

一个GetMessage也够学习好久的。这里先吧PreTranslateMessage看看。

//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到消息映射表找到消息入口,然后调用消息处理函数。

  第二,消息的目标窗口的PreTranslateMessage函数首先得到消息处理权,如果函数返回FALSE,那么他的父窗口将得到消息的处理权,直 到主窗口;如果函数返回TRUE(表示消息已经被处理了),那么就不需要调用父类的PreTranslateMessage函数。这样,保证了消息的目标 窗口以及他的父窗口都可以有机会调用PreTranslateMessage--在消息发送到窗口之前进行预处理(如果自己处理完然后返回FALSE的话 -_-b),如果你想要消息不传递给父类进行处理的话,返回TRUE就行了。

  第三,如果消息的目标窗口和主窗口没有父子关系,那么再调用主窗口的PreTranslateMessage函数。为什么这样?由第二步知道,一个窗口的 父窗口不是主窗口的话,尽管它的PreTranslateMessage返回FALSE,主窗口也没有机会调用PreTranslateMessage 数。我们知道,加速键的转换一般在框架窗口的PreTranslateMessage函数中。

整个框架就是让每个消息的目标窗口(包括他的父窗口)都有机会参与消息到来之前的处理。[详见http://blog.csdn.net/AlfredLu/archive/2007/03/18/1532836.aspx]

 

  这个机制在一个无限循环中,不断地从消息队列中获取消息,并且保证了程序的线程消息能够得到机会处 理,窗口消息在预处理之后被发送到相应的窗口处理过程。那么,还有一点疑问,为什么要一会儿调用::PeekMessage,一会儿调用:: GetMessage呢,他们有什么区别?

  NOTE:一般来说,GetMessage被设计用来高效地从消息队列获取消息。如果队列中没有消息,那么函数GetMessage导致线程休眠(让出CPU时间)。而PeekMessage是判断消息队列中如果没有消息,它马上返回0,不会导致线程处于睡眠状态。

  在上面的phase1第一个内循环中用到了PeekMessage,它的参数PM_NOREMOVE表示并不从消息队列中移走消息,而是一个检测查询,如 果消息队列中没有消息他立刻返回0,如果这时线程空闲的话将会引起消息循环调用OnIdle处理过程(上面讲到了这个函数的重要性)。如果将:: PeekMessage改成::GetMessage(***),那么如果消息队列中没有消息,线程将休眠,直到线程下一次获得CPU时间并且有消息出现 才可能继续执行,这样,消息循环的空闲时间没有得到应用,OnIdle也将得不到执行。这就是为什么既要用::PeekMessage(查询),又要 ::GetMessage(做实际的工作)的缘故。

原创粉丝点击