您的位置:首页 > 其它

MFC的来龙去脉-----消息循环,找处理场所

2013-12-12 18:19 483 查看
一、MFC应用程序如何启动,并进入消息循环?

-启动的核心:

  建立主线程,进入程序的main函数,然后进入消息循环。

-路径:

  定义全局theApp对象->调用类默认构造函数->调用MFC库自动link的winmain初始化,及入口->调用AfxWinMain函数->启动消息循环

  以下文章介绍了在theApp对象构造后,如何执行到afxwinmain的:

/article/6336753.html

  以下是afxwinmain代码:

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);

int nReturnCode = -1;
7     CWinThread* pThread = AfxGetThread();
8     CWinApp* pApp = AfxGetApp();

// AFX internal initialization
11     if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;

// App global initializations (rare)
15     if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;

// Perform specific initializations
19     if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
29     nReturnCode = pThread->Run();

InitFailure:
AfxWinTerm();
return nReturnCode;
}


值得注意的是,CWinApp继承自CWinThread对象,其实pThread和pApp是指向同一个对象,即应用程序的主线程。

pThread->InitInstance();


  一般应用程序主线程对象会改写此虚函数,完成一些线程初始化的动作。对于基于对话框的程序,在改写中建立模式对话框,在run之前启动主窗口的消息循环,代替run的作用。

nReturnCode = pThread->Run();


  Run函数一般不会被改写,调用的是基类的实现CWinThread::Run。它是MFC程序实现消息循环的主体:

int CWinThread::Run()
{
ASSERT_VALID(this);
_AFX_THREAD_STATE* pState = AfxGetThreadState();//获取当前线程信息,包括windows对象的唯一句柄构成的句柄表

// 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(&(pState->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))
if (IsIdleMessage(&(pState->m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}

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


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

  可以看到,run函数是线程从消息队列中不断循环捕获消息的过程,其中PumpMessage()是核心推动泵,其推动的是消息找到其对应的窗口,或处理方式。随着其不断推进,消息将陆续被过滤,隐藏再深,PumpMessage也不会放弃 - -。

BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();

if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))//从消息队列中捕获消息并阻塞,直到捕捉到一条消息
{
// Note: prevents calling message loop things in 'ExitInstance'
// will never be decremented
return FALSE;
}
// process this message

if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))//将消息分发至各个窗口

{
  ::TranslateMessage(&(pState->m_msgCur)); //消息解析
  ::DispatchMessage(&(pState->m_msgCur)); //最终调用消息对应的处理函数
} return TRUE;


  AfxPreTranslateMessage是实现消息分发至各个窗口:

BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
{
//    ASSERT_VALID(this);

CWinThread *pThread = AfxGetThread();
if( pThread )
{
// if this is a thread-message, short-circuit this function
if (pMsg->hwnd == NULL && pThread->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); //若存在主窗口,但消息指定的处理窗口的父窗口又不是主窗口,调用主窗口的PreTranslateMessage
}

return FALSE;   // no special processing
}


  WalkPreTranslateTree,从消息的目标窗口到父窗口,一级级往上查找是否存在窗口对象,若存在,则调用pWnd->PreTranslateMessage(pMsg),

若执行成功函数,返回TRUE,逐级返回至CWinThread::PumpMessage中,由接下来的Dispatch调用窗口过程处理函数,即真正的消息处理;

若已找到顶层父窗口(hWndStop指定),仍未找到合适的窗口对象,则逐级返回FALSE至CWinThread::PumpMessage中,意味着该消息不会被处理;

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
}


  若需提前截获消息,此虚函数可在对应的窗口中重载,让每个消息的目标窗口(包括他的父窗口)都有机会参与消息到来之前的处理:

pWnd->PreTranslateMessage(pMsg)


  若目标窗口是对话框对象(对于主窗口,一定是对话框对象),上述虚函数相当于执行重载了的CDialog::PreTranslateMessage,中间会过滤掉一些消息:

BOOL CDialog::PreTranslateMessage(MSG* pMsg)
{
// for modeless processing (or modal)
ASSERT(m_hWnd != NULL);

// allow tooltip messages to be filtered

if (CWnd::PreTranslateMessage(pMsg)) //Tooltip类消息,若需要处理,由其自己重新PreTranslateMessage重载处理
return TRUE;

// don't translate dialog messages when in Shift+F1 help mode
CFrameWnd* pFrameWnd = GetTopLevelFrame();  //Shift+F1 help mode下,不响应到该消息
if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
return FALSE;

// fix around for VK_ESCAPE in a multiline Edit that is on a Dialog
// that doesn't have a cancel or the cancel is disabled.
if (pMsg->message == WM_KEYDOWN &&
(pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_CANCEL) &&
(::GetWindowLong(pMsg->hwnd, GWL_STYLE) & ES_MULTILINE) &&
_AfxCompareClassName(pMsg->hwnd, _T("Edit")))
{
HWND hItem = ::GetDlgItem(m_hWnd, IDCANCEL);
if (hItem == NULL || ::IsWindowEnabled(hItem))
{
SendMessage(WM_COMMAND, IDCANCEL, 0);
return TRUE;
}
}
// filter both messages to dialog and from children
return PreTranslateInput(pMsg);
}


  过滤过的消息最终流向PreTranslateInput(pMsg)。此时消息仍未Dispatch,再过滤掉一些,只留下一些键盘、鼠标类的消息;

BOOL CWnd::PreTranslateInput(LPMSG lpMsg)
{
ASSERT(::IsWindow(m_hWnd));

// don't translate non-input events
if ((lpMsg->message < WM_KEYFIRST || lpMsg->message > WM_KEYLAST) &&
(lpMsg->message < WM_MOUSEFIRST || lpMsg->message > AFX_WM_MOUSELAST))
return FALSE;

return IsDialogMessage(lpMsg);
}


  IsDialogMessage来处理最后“幸存”的消息,下面是该函数的MSDN的解释:

Call this member function to determine whether the given message is intended for a modeless dialog box; if it is, this function processes the message. When the IsDialogMessage function processes a message, it checks for keyboard messages and converts them to selection commands for the corresponding dialog box. For example, the TAB key selects the next control or group of controls, and the DOWN ARROW key selects the next control in a group.


  这篇文章有对IsDialogMessage的分析:http://blog.csdn.net/yezigu921/article/details/8560807  

  首先,该消息必然是一些键盘或鼠标的消息,该消息被检查,看是否合适让一个无模式对话框处理;

  若仍然不处理,则返回FALSE,pumpmessage退回至Run的循环,GetMessage寻找下一条消息,周而复始;

  其次,被处理了,则返回非零值;那到底处理了一些什么消息?比如某些键盘消息,会被重新映射成某些对话框的选择,例如TAB键,映射成切换控件的快捷键;DOWN键,映射成选择同一个group的下个控件;

  被处理,意味着windows内核参与了,不过我们先不关心其具体实现。

  至此,PretranslateMessage若返回TRUE,表示消息被处理了(看最后的IsDialogMessage);若返回了FALSE,表示消息仍然没人要,那么继续返回pumpmessage,继续回到run中消息循环吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: