木目

或曰:狡兔三窟,非独其尔,吾亦为之。

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  6篇文章 :: 0篇收藏:: 1篇评论:: 0个Trackbacks

文章

收藏

相册

顾影自怜

翘首以盼

存档


正在读取评论……


——基于《深入浅出MFC》第九章&孙鑫20讲第六讲


当我在第一次看《深入浅出MFC》的时候,很多东西都是似懂非懂,这种感觉一直持续到我有机会结合孙鑫老师的VC20讲一起看,孙鑫老师的20讲,简直就是《深入浅出MFC》的视频教程,这二者的结合真是太完美了!而且我一直一来看书的习惯就是博采众家之长而为我所用。这一次的学习过程从某种意义上讲,的确是一种享受。

既然MFC在本质上是以消息为基础的,在大致了解了MFC的消息机制之后,我觉得有必要深入理解MFC的消息映射与命令传递机制。

一、消息分类:

1、标准消息:

除WM_COMMAMD之外,所有以WM_开头的消息,凡派生自CWnd的类,可接收该类消息。

对于标准消息,可以在某个窗口类

2、命令消息:

WM_COMMAND称为命令消息,由菜单、加速键(快捷键)、工具栏按钮产生,由消息的wParam标识命令消息来自哪儿,凡派生自CCmdTarget的类,可接收该类消息。

3、通告消息:

由控件产生,向父窗口(对话框)通知发生的事件,该类消息也是以WM_COMMAND形式呈现的,凡派生自CCmdTarget的类,可接收该类消息。

本来,消息对于我们来说是一个非常简单的,只是我们对其分类与处理没有一个明确的界定罢了,而到了现在,或许唯一的障碍来自我们尚不清楚消息是怎样被映射到函数的,难道是上帝之手吗?

二、消息映射:

1、DECLARE_MESSAGE_MAP():

既然消息在诸多类中被接收并处理,我们是否应该在类中事先“声明”呢?是的,我们当然必须在类的声明文件(.H)中声明,只不过我们声明的不是什么消息和函数,而是消息映射表格。刚开始的时候,我曾想过用virtual函数吧!但是MFC的消息有上百种之多,哪些消息需要,哪些消息不需要,这种问题,因时而易,MS也说不清楚,况且,经过若干次的继承,这样一个虚函数表的大小想必也是非常可观了。提到精简,为什么不想到静态函数呢?于是,MS发明了消息映射表格,下面是一个消息映射表格的例子:

//{{AFX_MSG(CWin_DrawView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

//{{和//}}中间就是所谓的注释宏,每一项由消息对应宏名和处理函数构成。通过其后的DECLARE_MESSAGE_MAP()把类中需要处理的多种消息和类联系在一起。

DECLARE_MESSAGE_MAP()的定义:

<AFXWIN.H>

#define DECLARE_MESSAGE_MAP() \

private: \

static const AFX_MSGMAP_ENTRY _messageEntries[]; \

protected: \

static AFX_DATA const AFX_MSGMAP messageMap; \

static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \

virtual const AFX_MSGMAP* GetMessageMap() const; \

AFX_MSGMAP_ENTRY的定义:

struct AFX_MSGMAP_ENTRY

{

UINT nMessage; // windows message

UINT nCode; // control code or WM_NOTIFY code

UINT nID; // control ID (or 0 for windows messages)

UINT nLastID; // used for entries specifying a range of control id's

UINT nSig; // signature type (action) or pointer to message #

AFX_PMSG pfn; // routine to call (or special value)

};

AFX_MSGMAP的定义:

struct AFX_MSGMAP

{


const AFX_MSGMAP* pBaseMap;

const AFX_MSGMAP_ENTRY* lpEntries;

};

2、BEGIN_MESSAGE_MAP与END_MESSAGE_MAP()

声明过后,现在就要在类的应用程序文件(.CPP)中加以实现了。下面几个宏比较easy:

<AFXWIN.H>

BEGIN_MESSAGE_MAP(theClass, baseClass)的定义:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \

const AFX_MSGMAP* theClass::GetMessageMap() const \

{ return &theClass::messageMap; } \

AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \

{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \

AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \

{ \

END_MESSAGE_MAP()的定义:

#define END_MESSAGE_MAP() \

{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \

}; \

eg.

BEGIN_MESSAGE_MAP(CWin_DrawView, CView)

//{{AFX_MSG_MAP(CWin_DrawView)

ON_WM_LBUTTONDOWN()

ON_WM_LBUTTONUP()

ON_WM_MOUSEMOVE()

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

在这儿,还有一点需要说明的应该是,并不是所有派生自CCmdTarget的类都有DECLARE/BEGIN/END宏组,CWinThread没有,到现在为止,我还不清楚这个CWinThread到底有多么重要,只是记得它在AfxWinMain中用到过(线程嘛!肯定要用了!)。但我知道,有一个很重要的类CWinApp是派生自CWinThread的,所以,对于CWinApp,其BEGIN_MESSAGE_MAP的baseClass变成了CCmdTarget,有点乱伦,但尚可忍受。

三、消息捕获:

同样是在AftWinMain中,在调用pApp->InitApplication()和pThread->InitInstance()之前调用了AfxWinInit(hInstance,hPrevInstance, lpCmdLine,nCmdShow),在这个函数中,注册了四个窗口类,它们具有相同的处理函数AfxWndProc。MFX利用hook(传说中的钩子吧)抓取消息,送往滤网函数(filer),这个过程对于目前的我的确有点高深,而且并不真正影响我对消息捕获的理解。所以,我可以暂时认为消息被钩子钩出来了,就这么简单……

四、消息传递:

既然各窗口的消息均由AfxWndProc处理,我想要想知道消息是怎样被传递的,只须继续跟踪这个函数就是了。

1、<WINCORE.CPP>:LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

{

......

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

......

if (pWnd == NULL || pWnd->m_hWnd != hWnd) // 当窗口指针无法创建或不是当前消息捕获窗口时,调用默认窗口过程函数

return ::DefWindowProc(hWnd, nMsg, wParam, lParam);

return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

}

2、<WINCORE.CPP>:LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,

WPARAM wParam = 0, LPARAM lParam = 0)

{

......

LRESULT lResult;

......

lResult = pWnd->WindowProc(nMsg, wParam, lParam); // 看来这才是真正的窗口过程函数

......

return lResult;

}

3、<WINCORE.CPP>:LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

LRESULT lResult = 0;

if (!OnWndMsg(message, wParam, lParam, &lResult)) // 分辨并处理消息

lResult = DefWindowProc(message, wParam, lParam);

return lResult;

}

4、<WINCORE.CPP>:BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

{

if (message == WM_COMMAND) // 处理命令消息

{

if (OnCommand(wParam, lParam))

......

}


// special case for notifies

if (message == WM_NOTIFY) // 处理通告消息

{

NMHDR* pNMHDR = (NMHDR*)lParam;

if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))

......

}


// special case for activation

if (message == WM_ACTIVATE) // 处理各种标准消息

_AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));


......

AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];

......

if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)


}

这个函数的工作就是反复比较消息映射表和Cache,是的,的确是Cache,这是我第一次在教科书之外如此真实的接触它,但是MS还是为我们隐藏了实现的细节。所有标准消息在OnWndMsg中被传递给对应处理函数,而命令消息和通告消息则被分别交给了OnCommand和OnNotify,这样两个函数都是虚函数,在CWnd的派生类中将其改写。

5、我现在急于知道的另一问题是:当多个类都建立了对同一个消息的响应函数时,其响应的优先顺序是怎么样的呢?假设消息被CFrameWnd获得,来看一下CFrameWnd改写的OnCmdMsg:

<WINFRM.CPP>:BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)

{

......

// route as normal command

return CWnd::OnCommand(wParam, lParam);

}


<WINCORE.CPP>:BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)

{

......

return OnCmdMsg(nID, nCode, NULL, NULL); // CWnd::OnCmdMsg继承自CCmdTarget而未改写它

}


<CMDTARG.CPP>:BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)

{

......

return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL);

}


<CMDTARG.CPP>:BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{

......

for (pMessageMap = GetMessageMap(); pMessageMap != NULL;

pMessageMap = pMessageMap->pBaseMap)

{

lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);

if (lpEntry != NULL)

{

// found it

return _AfxDispatchCmdMsg(this, nID, nCode,

lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);

}

}

return FALSE; // not handled

}


<WINFRM.CPP>:BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{

CPushRoutingFrame push(this);


// pump through current view FIRST

CView* pView = GetActiveView();

if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;


// then pump through frame

if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;


// last but not least, pump through app

CWinApp* pApp = AfxGetApp();

if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;


return FALSE;

}


<VIEWCORE.CPP>:BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{

// first pump through pane

if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;


// then pump through document

if (m_pDocument != NULL)

{

// special state for saving view before routing to document

CPushRoutingView push(this);

return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}


return FALSE;

}

由此可见,同一个消息虽可被不同类捕获,但各个类响应优先次序应该是:CView、CDocument、CMainFrame、CWinApp,当一个消息产生后,遵循这一传递路线,直到消息被处理或最终传给CWinApp,由DefWindowProc处理。

五、盖棺论定:

当我们继续跟踪,我们又会发现在OnWndMsg对标准消息的处理、OnCmdMsg对WM_COMMAND和WM_NOTIFY的处理中,所有消息又回到了一起,它们有一个共同的前缀AfxSig_,侯俊杰称之为“罗塞达碑石”,换言之,揭开消息映射最后谜底的关键吧!对于我这样的初学者,我只是发现到了这是,我再也不想去跟踪接下来的union MessageMapFunction和enum AfxSig了!The fuckingMESSAGE MAP. Drop dead!


2006/6/14 20:56:21


Trackback: http://tb.donews.net/TrackBack.aspx?PostId=920571


[点击此处收藏本文]  发表于2006年06月19日 2:47 AM




正在读取评论……

发表评论

大名:
网址:
验证码
评论