关于MFC 消息映射机制剖析.docx
《关于MFC 消息映射机制剖析.docx》由会员分享,可在线阅读,更多相关《关于MFC 消息映射机制剖析.docx(15页珍藏版)》请在冰豆网上搜索。
![关于MFC 消息映射机制剖析.docx](https://file1.bdocx.com/fileroot1/2022-12/14/437fe6e4-deea-45ec-80b4-a620b7c9f488/437fe6e4-deea-45ec-80b4-a620b7c9f4881.gif)
关于MFC消息映射机制剖析
软采用了所谓的消息映射机制,来完成不同对象之间消息的传递,本文就MFC9.0源码进行分析,大致讲解MFC的消息映射机制。
步入正题,在AfxWinMain()函数中,当MFC框架初始化完成后,即pThread->InitInstance()执行完成,就开始进行消息循环,入口函数是pThread->Run():
[cpp] viewplaincopy
1.// Perform specific initializations
2.if (!
pThread->InitInstance())// MFC初始化框架
3.{
4. if (pThread->m_pMainWnd !
= NULL)
5. {
6. TRACE(traceAppMsg, 0, "Warning:
Destroying non-NULL m_pMainWnd\n");
7. pThread->m_pMainWnd->DestroyWindow();
8. }
9. nReturnCode = pThread->ExitInstance();
10. goto InitFailure;
11.}
12.
nReturnCode = pThread->Run();// 进入消息循环
执行CWinApp:
:
Run():
[cpp] viewplaincopy
1.// Main running routine until application exits
2.int CWinApp:
:
Run()
3.{
4. if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
5. {
6. // Not launched /Embedding or /Automation, but has no main window!
7. TRACE(traceAppMsg, 0, "Warning:
m_pMainWnd is NULL in CWinApp:
:
Run - quitting application.\n");
8. AfxPostQuitMessage(0);
9. }
10. return CWinThread:
:
Run();
11.}
执行CWinThread:
:
Run():
[cpp] viewplaincopy
1.// main running routine until thread exits
2.int CWinThread:
:
Run()
3.{
4. ASSERT_VALID(this);
5. _AFX_THREAD_STATE* pState = AfxGetThreadState();
6.
7. // for tracking the idle time state
8. BOOL bIdle = TRUE;
9. LONG lIdleCount = 0;
10.
11. // acquire and dispatch messages until a WM_QUIT message is received.
12.GetMessage:
从系统获取消息,将消息从系统中移除,属于阻塞函数。
当系统无消息时,GetMessage会等待下一条消息。
而函数PeekMesssge是以查看的方式从系统中获取消息,可以不将消息从系统中移除,是非阻塞函数;当系统无消息时,返回FALSE,继续执行后续代码
13. for (;;)
14. {
15. // phase1:
check to see if we can do idle work
16. while (bIdle &&
17. !
:
:
PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
18. {
19. // call OnIdle while in bIdle state
20. if (!
OnIdle(lIdleCount++))
21. bIdle = FALSE; // assume "no idle" state
22. }
23.
24. // phase2:
pump messages while available
25. do
26. {
27. // pump message, but quit on WM_QUIT
28. if (!
PumpMessage())
29. return ExitInstance();
30.
31. // reset "no idle" state after pumping "normal" message
32. //if (IsIdleMessage(&m_msgCur))
33. if (IsIdleMessage(&(pState->m_msgCur)))
34. {
35. bIdle = TRUE;
36. lIdleCount = 0;
37. }
38.
39. } while (:
:
PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)); //查看消息队列中是否有消息
40. }
41.}
在do-while循环中进行消息的路由,主要函数就是 PumpMessage(),我们跟着进入这个函数看看做了什么:
[cpp] viewplaincopy
1.BOOL CWinThread:
:
PumpMessage()
2.{
3. return AfxInternalPumpMessage();
4.}
继续跟踪:
[cpp] viewplaincopy
1.BOOL AFXAPI AfxInternalPumpMessage()
2.{
3. _AFX_THREAD_STATE *pState = AfxGetThreadState();
4.
5. if (!
:
:
GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))// 从消息队列获取消息
6. {
7.#ifdef _DEBUG
8. TRACE(traceAppMsg, 1, "CWinThread:
:
PumpMessage - Received WM_QUIT.\n");
9. pState->m_nDisablePumpCount++; // application must die
10.#endif
11. // Note:
prevents calling message loop things in 'ExitInstance'
12. // will never be decremented
13. return FALSE;
14. }
15.
16.#ifdef _DEBUG
17. if (pState->m_nDisablePumpCount !
= 0)
18. {
19. TRACE(traceAppMsg, 0, "Error:
CWinThread:
:
PumpMessage called when not permitted.\n");
20. ASSERT(FALSE);
21. }
22.#endif
23.
24.#ifdef _DEBUG
25. _AfxTraceMsg(_T("PumpMessage"), &(pState->m_msgCur));
26.#endif
27.
28. // process this message
29.
30.
if (pState->m_msgCur.message !
= WM_KICKIDLE && !
AfxPreTranslateMessage(&(pState->m_msgCur)))
31. {
32.:
:
TranslateMessage(&(pState->m_msgCur));
33.:
:
DispatchMessage(&(pState->m_msgCur));
34. }
35. return TRUE;
36.}
从上面的代码我们可以看到MFC是通过 GetMessage()来获取消息,然后再看下面这几句代码:
[cpp] viewplaincopy
1.if (pState->m_msgCur.message !
= WM_KICKIDLE && !
AfxPreTranslateMessage(&(pState->m_msgCur)))
2.{
3. :
:
TranslateMessage(&(pState->m_msgCur));
4. :
:
DispatchMessage(&(pState->m_msgCur));
5.}
也就是说当系统获取消息后,先调用 AfxPreTranslateMessage()这个看起来跟我们经常看到的PreTranslateMessage()很像!
我们来看看到底发生了什么:
[cpp] viewplaincopy
1.BOOL __cdecl AfxPreTranslateMessage(MSG* pMsg)
2.{
3. CWinThread *pThread = AfxGetThread();
4. if( pThread )
5. return pThread->PreTranslateMessage( pMsg );
6. else
7. return AfxInternalPreTranslateMessage( pMsg );
8.}
执行 pThread->PreTranslateMessage():
[cpp] viewplaincopy
1.BOOL CWinThread:
:
PreTranslateMessage(MSG* pMsg)
2.{
3. ASSERT_VALID(this);
4. return AfxInternalPreTranslateMessage( pMsg );
5.}
执行AfxInternalPreTranslateMessage():
[cpp] viewplaincopy
1.BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
2.{
3.// ASSERT_VALID(this);
4.
5. CWinThread *pThread = AfxGetThread();
6. if( pThread )
7. {
8. // if this is a thread-message, short-circuit this function
9. if (pMsg->hwnd == NULL && pThread->DispatchThreadMessageEx(pMsg))
10. return TRUE;
11. }
12.
13. // walk from target to main window
14. CWnd* pMainWnd = AfxGetMainWnd();
15. if (CWnd:
:
WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))// 注意这个函数
16. return TRUE;
17.
18. // in case of modeless dialogs, last chance route through main
19. // window's accelerator table
20. if (pMainWnd !
= NULL)
21. {
22. CWnd* pWnd = CWnd:
:
FromHandle(pMsg->hwnd);
23. if (pWnd->GetTopLevelParent() !
= pMainWnd)
24. return pMainWnd->PreTranslateMessage(pMsg);
25. }
26.
27. return FALSE; // no special processing
28.}
执行CWnd:
:
WalkPreTranslateTree(pMainWnd->GetSafeHwnd(),pMsg)
[cpp] viewplaincopy
1.BOOL PASCAL CWnd:
:
WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
2.{
3. ASSERT(hWndStop == NULL || :
:
IsWindow(hWndStop));
4. ASSERT(pMsg !
= NULL);
5.
6. // walk from the target window up to the hWndStop window checking
7. // if any window wants to translate this message
8.
9. for (HWND hWnd = pMsg->hwnd; hWnd !
= NULL; hWnd = :
:
GetParent(hWnd))
10. {
11. CWnd* pWnd = CWnd:
:
FromHandlePermanent(hWnd);
12. if (pWnd !
= NULL)
13. {
14. // target window is a C++ window
15. if (pWnd->PreTranslateMessage(pMsg))
16. return TRUE; // trapped by target window (eg:
accelerators)
17. }
18.
19. // got to hWndStop window without interest
20. if (hWnd == hWndStop)
21. break;
22. }
23. return FALSE; // no special processing
24.}
MFC在后台维护了一个句柄和C++对象指针的映射表,一旦有消息产生时,我们知道这个消息的结构体中包含了该消息所属窗口的句柄,那么通过这个句柄我们可以找到相对于的C++对象的指针,在for循环里面遍历当前窗口的所有父窗口,查找是否有消息重写,一旦有子类重写父类消息的,则通过当前消息所属窗口的句柄来调用CWnd:
:
FromHandlePermanent(hWnd) 函数,从而得到当前C++对象的指针,最后调用PreTranslateMessage(),因为 PreTranslateMessage()是虚函数,所以调用的是子类的 PreTranslateMessage(),该函数可以自定义一部分消息的处理方式。
需要注意的是,PreTranslateMessage()返回为FALSE时,说明没有发生消息的重写,则把消息直接给 TranslateMessage()和DispatchMessage()进行处理,当返回是TRUE时,则去消息队列获取下一条消息。
另外,SendMessage()是直接发送给WindowProc进行处理(该函数是一个DispatchMessage()中的一个回调函数,用于处理默认的一些系统消息),没有进入消息队列,所以不会被GetMessage()抓取到,所以也就不会PreTranslateMessage()抓到了,但是PostMessage()是进入消息队列的,是可以被GetMessage()抓到的。
所以从上面的代码跟踪可见,当我们需要重写一些系统消息时,比如给程序设置一些快捷键等,可以在PreTranslateMessage()中进行操作,当然,不是PreTranslateMessage()并不是可以重写所有消息,有一些消息也是无法处理的,这时可以交给WindowProc进行处理,WindowProc()也是虚函数。
[cpp] viewplaincopy
1.LRESULT CWnd:
:
WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
2.{
3. // OnWndMsg does most of the work, except for DefWindowProc call
4. LRESULT lResult = 0;
5.
if (!
OnWndMsg(message, wParam, lParam, &lResult))
6. lResult = DefWindowProc(message, wParam, lParam);
7. return lResult;
8.}
消息的映射主要是通过OnWndMsg()进行处理,考虑到该函数代码很多,就不全贴了,我们分析其中一小段:
[cpp] viewplaincopy
1.for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap !
= NULL;
2. pMessageMap = (*pMessageMap->pfnGetBaseMap)())
3.{
4. // Note:
catch not so common but fatal mistake!
!
5. // BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
6. ASSERT(pMessageMap !
= (*pMessageMap->pfnGetBaseMap)());
7. if (message < 0xC000)
8. {
9. // constant window message
10. if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
11. message, 0, 0)) !
= NULL)
12. {
13. pMsgCache->lpEntry = lpEntry;
14. winMsgLock.Unlock();
15. goto LDispatch;
16. }
17. }
18. else
19. {
20. // registered windows message
21. lpEntry = pMessageMap->lpEntries;
22. while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) !
= NULL)
23. {
24. UINT* pnID = (UINT*)(lpEntry->nSig);
25. ASSERT(*pnID >= 0xC000 || *pnID == 0);
26. // must be successfully registered
27. if (*pnID == message)
28. {
29. pMsgCache->lpEntry = lpEntry;
30. winMsgLock.Unlock();
31. goto LDispatchRegistered;
32. }
33. lpEntry++; // keep looking past this one
34. }
35. }
36.}
在for循环里面有这样一个数据:
AFX_MSGMAP* pMessageMap(),它代表当前C++对象的消息映射表指针,那么这里的消息映射表到底是个什么样的数据结构,我们来一探究竟。
我们打开