1. 程式人生 > >VC++/MFC訊息對映機制(3):MFC訊息路由的原始碼分析

VC++/MFC訊息對映機制(3):MFC訊息路由的原始碼分析

VC++/MFC訊息對映機制(3):MFC訊息路由的原始碼分析

若對C++語法不熟悉,建議參閱《C++語法詳解》一書,電子工業出版社出版,該書語法示例短小精悍,對查閱C++知識點相當方便,並對語法原理進行了透徹、深入詳細的講解。
注意:本文最好結合本人所作前兩篇與訊息對映機制有關的文章一起閱讀。

注意:訊息路由包含了訊息對映功能
1、MFC原始碼的訊息對映巨集,把模仿程式中的訊息對映陣列ss和訊息對映表msgmp封裝在了名為GetThisMessageMap()的函式內部。但其基本原理是相同的,請讀者自行理解。
2、基本原理:在MFC原始碼中,訊息路由的功能是在重新設定的過程函式::AfxWndProc()內部間接完成的,因此在MFC原始碼中的第一步就是使用鉤子攔截訊息,並重新設定過程函式。
3、首先使用鉤子函式攔截訊息,以實現訊息對映。在MFC原始碼中是使用AfxHookWindowCreate()函式間接呼叫::SetWindowsHookEx(…)函式,設定鉤子函式的。AfxHookWindowCreate是在CWnd::CreateEx函式中的PreCreateWindow函式(完成視窗註冊)和::CreateWindowEx函式(完成視窗建立)之間呼叫的。
4、然後通過鉤子函式間接的使用SetWindowLongPtr()函式,重新設定程式的過程函式為::AfxWndProc()。
5、過程函式AfxWndProc並不直接處理訊息,而是通過呼叫CWnd::WindowProc函式處理由鉤子攔截到的訊息。訊息路由的功能就是在該函式內通過呼叫OnWndMsg()間接完成的。
6、CWnd::WindowProc根據訊息的不同情形,分別使用CWnd::OnWndMsg()函式和CWnd::DefWindowProc處理訊息。其中CWnd::OnWndMsg()是主要的處理訊息的函式,即訊息對映和訊息路由的功能都在該函式內直接或間接的實現。
7、CWnd::OnWndMsg()函式,根據接收到的訊息型別的不同,而對訊息進行不同的路由,若訊息是命令訊息WM_COMMAND,則呼叫OnCommand()虛擬函式處理。若訊息是通知訊息WM_NOTIFY則呼叫OnNotify虛擬函式進行處理,若訊息是一般訊息,則在CWnd::OnWndMsg()函式內部直接進行處理,對於一般訊息,使用的是直線路由模式,因此CWnd::OnWndMsg內部實現了直線路由。
8、對於OnCommand虛擬函式,因為處理的是命令訊息,而命令訊息可能會拐彎,因此在OnCommand函式內部實現了拐彎路由。
在這裡插入圖片描述

一、使用鉤子函式重新設定過程函式(以下程式全部在wincore.cpp檔案內實現)
注:鉤子函式的原理見後文附註講解
以下程式全部來自wincore.cpp檔案內部的MFC原始碼程式

BOOL CWnd::CreateEx(......){     //節選原始碼
	......
	if (!PreCreateWindow(cs)){PostNcDestroy();return FALSE;}
	AfxHookWindowCreate(this);  //❶、由該函式間接設定鉤子函式。
	HWND hWnd = CreateWindowEx(...);  }

void AFXAPI AfxHookWindowCreate(CWnd* pWnd)  //完整原始碼
{	_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
	if (pThreadState->m_pWndInit == pWnd)	return;
	if (pThreadState->m_hHookOldCbtFilter == NULL)
	{	pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,  //設定鉤子函式
			_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());  //❷、粗體為設定的鉤子函式
		if (pThreadState->m_hHookOldCbtFilter == NULL)	AfxThrowMemoryException();
	}
//以下為一些發生錯誤時的處理程式碼。
	ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);
	ASSERT(pWnd != NULL);
	ASSERT(pWnd->m_hWnd == NULL);   // only do once
	ASSERT(pThreadState->m_pWndInit == NULL);   // hook not already in progress
	pThreadState->m_pWndInit = pWnd;}

LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam){//節選原始碼
......
if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL)){
......
if (pWndInit != NULL){
......
WNDPROC afxWndProc = AfxGetAfxWndProc();  //❸、粗體為重新設定的過程函式
oldWndProc = (WNDPROC)SetWindowLongPtr(    //重新設定過程函式為❸的反回值。
hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
......}

WNDPROC AFXAPI AfxGetAfxWndProc(){//完整原始碼
#ifdef _AFXDLL
	return AfxGetModuleState()->m_pfnAfxWndProc;
#else
	return &AfxWndProc;  //❹、粗體為重新設定的過程函式,至此過程函式重新設定完畢。
#endif
}

LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)//完整原始碼
{
	// special message which identifies the window as using AfxWndProc
	if (nMsg == WM_QUERYAFXWNDPROC)	return 1;
	// all other messages route through message map
	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);  //表示獲取視窗控制代碼hWnd所關聯的類物件。
	ASSERT(pWnd != NULL);					
	ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
	if (pWnd==NULL||pWnd->m_hWnd!=hWnd) return ::DefWindowProc(hWnd,nMsg,wParam,lParam);
	return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);}//❺、呼叫此函式完成過程函式的功能

LRESULT AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,WPARAM wParam = 0, LPARAM lParam = 0)
......
if (nMsg == WM_INITDIALOG)
			_AfxPreInitDialog(pWnd, &rectOld, &dwStyle);
lResult=pWnd->WindowProc(nMsg,wParam,lParam); //❻、呼叫父類的該函式完成過程函式的功能。
...... }

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam){//完整原始碼
	// OnWndMsg does most of the work, except for DefWindowProc call
	LRESULT lResult = 0;
	if (!OnWndMsg(message, wParam, lParam, &lResult)) //❼、訊息路由從此成員函式開始
		lResult = DefWindowProc(message, wParam, lParam);
	return lResult;
}

二、MFC原始碼實現直線路由(CWnd::OnWndMsg原始碼解析wincore.cpp)

1、基本原理:直線路由的實現原理請參閱模仿直線路由的講解(示例3.10)。在MFC原始碼中是使用CWnd::OnWndMsg實現直線路由和拐彎路由的,其中直線路由由該函式內部直接實現,而拐彎路由通過呼叫另一個虛擬成員函式CWnd::OnCommand間接實現,另外OnWndMsg還對不同型別的訊息,進行了不同的處理,詳見原始碼。
2、存放<訊息,處理函式>對的結構體(afxwin.h)

struct AFX_MSGMAP_ENTRY { UINT nMessage;   	UINT nCode;   	UINT nID;        	
UINT nLastID;    	UINT_PTR nSig;      	AFX_PMSG pfn;   };

3、訊息對映表(afxwin.h)

struct AFX_MSGMAP
{const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();	const AFX_MSGMAP_ENTRY* lpEntries;};

4、用於遍歷訊息對映陣列的函式

const AFX_MSGMAP_ENTRY* AFXAPI
AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,UINT nMsg, UINT nCode, UINT nID){
#if defined(_M_IX86)
			......  //在這之中是一系列的彙編程式碼,用於加快處理速度。
#else
while (lpEntry->nSig != AfxSig_end)   /*⓬、此while用於遍歷訊息對映陣列_messageEntries(該陣列在巨集BEGIN_MESSAGE_MAP之中宣告和定義中的所有<訊息,處理函式>對。其原理與模仿程式示例3.10中的while語句相同。*/
{if (lpEntry->nMessage == nMsg   /*⓭、比對從視窗接收到的訊息是否與訊息對映陣列_messageEntries中<訊息,處理函式>對裡的訊息相等*/
&& lpEntry->nCode == nCode &&nID >= lpEntry->nID && nID <= lpEntry->nLastID)
			{	return lpEntry;	}
		lpEntry++;} //while結束
		return NULL;    // not found
#endif   }

5、實現訊息路由的共用體

union MessageMapFunctions{
		BOOL (AFX_MSG_CALL CWnd::*pfn_b_D_v)(CDC*);
		BOOL (AFX_MSG_CALL CWnd::*pfn_b_b_v)(BOOL);
		BOOL (AFX_MSG_CALL CWnd::*pfn_b_W_uu)(CWnd*,UNIT,UNIT);
		BOOL (AFX_MSG_CALL CWnd::*pfn_l_w_l)(WPARAM,LPARAM);
...... }  //大約有一百多行類似的程式碼

6、需要用到的列舉

enum AfxSig     //位於檔案afxmsg_.h
{	AfxSig_end = 0,     // [marks end of message map]
		AfxSig_b_D_v,				// BOOL (CDC*)
		AfxSig_b_b_v,				// BOOL (BOOL)
		AfxSig_b_u_v,				// BOOL (UINT)
		AfxSig_b_h_v,				// BOOL (HANDLE)
		AfxSig_b_W_uu,				// BOOL (CWnd*, UINT, UINT)
......  } //大約有一百多行類似的程式碼

7、Cwnd::OnWndMsg原始碼(wincore.cpp)

Cwnd::OnWndMsg原始碼中會用到由訊息對映巨集DECLARE_MESSAGE_MAP()、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP、ON_MESSAGE展開後的程式碼,比如其中的GetMessageMap()函式。

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{//絕大部分原始碼
	LRESULT lResult = 0;
	union MessageMapFunctions mmf;   //❶、共用體的使用(原理見示例3.5),其定義見後文。
	mmf.pfn = 0;
	CInternalGlobalLock winMsgLock;
	// special case for commands
	if (message == WM_COMMAND)     //❷、判斷訊息是否是命令訊息,若是,則按以下方式處理。
	{	if (OnCommand(wParam, lParam))   //❸、使用OnCommand實現拐彎路由
		{lResult = 1;	goto LReturnTrue;}
		return FALSE;	}
……
	if (message == WM_NOTIFY)     //判斷訊息是否是通知訊息WM_NOTIFY
		{NMHDR* pNMHDR = (NMHDR*)lParam;
		if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))goto LReturnTrue;
		return FALSE;	}
	if (message == WM_ACTIVATE)   //判斷是否是WM_ACTIVATE訊息
		_AfxHandleActivate(this, wParam, CWnd::FromHandle((HWND)lParam));
	if (message == WM_SETCURSOR &&
		_AfxHandleSetCursor(this, (short)LOWORD(lParam), HIWORD(lParam)))
		{lResult = 1;
		goto LReturnTrue;}
   // special case for windows that contain windowless ActiveX controls
   BOOL bHandled;
   bHandled = FALSE;
   if ((m_pCtrlCont != NULL) && (m_pCtrlCont->m_nWindowlessControls > 0))
   {	  if (((message >= WM_MOUSEFIRST) && (message <= AFX_WM_MOUSELAST)) ||
		 ((message >= WM_KEYFIRST) && (message <= WM_IME_KEYLAST)) ||
		 ((message >= WM_IME_SETCONTEXT) && (message <= WM_IME_KEYUP)))
	  {	bHandled = m_pCtrlCont->HandleWindowlessMessage(message, wParam, lParam, &lResult); }
   }
if (bHandled)   {	  goto LReturnTrue;		 }
……

	const AFX_MSGMAP* pMessageMap; 
pMessageMap = GetMessageMap(); 
	const AFX_MSGMAP_ENTRY* lpEntry;

UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
	winMsgLock.Lock(CRIT_WINMSGCACHE);
	AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];

if (message == pMsgCache->nMsg &&           //檢查訊息是否處於cache之中
 pMessageMap == pMsgCache->pMessageMap) 
	{	// cache hit
		lpEntry = pMsgCache->lpEntry;
		winMsgLock.Unlock();
		if (lpEntry == NULL)	return FALSE;

		// cache hit, and it needs to be handled
		if (message < 0xC000)	goto LDispatch;
		else	goto LDispatchRegistered;	}
else                                    //❺、從此處開始實現訊息的直線路由
	{	pMsgCache->nMsg = message;
		pMsgCache->pMessageMap = pMessageMap;
/*❻、for迴圈的原理與直線路由模仿程式示例3.10相同。但要注意的是在MFC之中,把訊息對映(模仿示例中的ss,在MFC中為_messageEntries)和訊息對映表(模仿示例中的msgmp,在MFC中為messageMap)封裝在了一個名為GetThisMessageMap的函式之中,但其基本原理是一樣的。*/ 
		for (;pMessageMap->pfnGetBaseMap != NULL; 
pMessageMap = (*pMessageMap->pfnGetBaseMap)() )
{	ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
if (message < 0xC000)   /*小於0xC000的訊息為一般訊息,否則為使用者註冊訊息。*/
			{
/*❼、函式AfxFindMessageEntry的主要作用是用於迴圈查詢訊息對映(即_messageEntries)中的所有<訊息,處理函式>對,該函式類似於模仿程式示例3.10中的while語句除去其中的swtich後的樣子。其定義見後文。*/
				if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
					message, 0, 0)) != NULL)
				{ pMsgCache->lpEntry = lpEntry;
				  winMsgLock.Unlock();
goto LDispatch;   /*❽、LDispatch後的語句,主要用於比對應該呼叫共用體中的哪一個函式。類似於模仿程式示例3.10中的swtich語句。*/
				} }
			else  		// registered windows message,處理使用者註冊訊息。
			{	lpEntry = pMessageMap->lpEntries;
				while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
				{	UINT* pnID = (UINT*)(lpEntry->nSig);
					ASSERT(*pnID >= 0xC000 || *pnID == 0);
					if (*pnID == message)
					{	pMsgCache->lpEntry = lpEntry;
						winMsgLock.Unlock();
						goto LDispatchRegistered;
					}
					lpEntry++;      // keep looking past this one
		}}	}   //for結束

		pMsgCache->lpEntry = NULL;
		winMsgLock.Unlock();
		return FALSE;	}  	 //if結束
LDispatch:
	ASSERT(message < 0xC000);
	mmf.pfn = lpEntry->pfn;   //❾、初始化共用體
switch (lpEntry->nSig)  /*❿、根據訊息處理函式原型的不同,而使用共用體中不同的成員間接呼叫消處理函式,以下呼叫的原則是,共用體mmf中指向函式的指標成員的原型必須與訊息處理函式的原型相同。*/
	{ default:	ASSERT(FALSE);	break;
case AfxSig_l_p:    /*⓫、AfxSig_XXXX是列舉型別AfxSig中定義的列舉值,其定義見後文,該列舉是與MessageMapFunctions共用體相對應的。*/
{CPoint point(lParam);lResult=(this->*mmf.pfn_l_p)(point);	break;}
	  case AfxSig_b_D_v:
lResult = (this->*mmf.pfn_b_D)(CDC::FromHandle(reinterpret_cast<HDC>(wParam)));break;
......  //此處大約有100多個類似的語句。
}
LDispatchRegistered:    // for registered windows messages
	ASSERT(message >= 0xC000);
	ASSERT(sizeof(mmf) == sizeof(mmf.pfn));
	mmf.pfn = lpEntry->pfn;
	lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);
LReturnTrue:
	if (pResult != NULL)
		*pResult = lResult;
	return TRUE;	}                      //OnWndMsg結束

三、MFC原始碼實現拐彎路由(CWnd::OnCommand原始碼解析wincore.cpp)
在這裡插入圖片描述

1、拐彎路由牽扯到幾個問題,第一是訊息是從哪個類傳入進去的,第二個是拐彎路由時的函式呼叫,第三個是繼承結構問題,因此在講解原始碼的訊息拐彎路由時需作一些假設。
2、因為在CWnd::OnWndMsg中呼叫的OnCommand是個虛擬函式,因此究竟是呼叫的哪個類中的OnCommand虛擬函式,需要視this指標指向的型別而定。以下類都重寫了該虛擬函式CWnd,CFrameWnd,CSplitterWnd,CPropertySheet,CMDIFrameWnd,COlePropertyPage
3、OnCommand函式並不會執行訊息的匹配和查詢工作,該函式的主要作用是實現訊息的拐彎,訊息的匹配和查詢工作由另一個虛擬函式OnCmdMsg函式完成,該虛擬函式主要用於實現訊息在繼承子樹中的直線路由,因此程式最終都會呼叫OnCmdMsg函式,以實現該類所在繼承子樹的直線路由,而該函式的最終實現程式碼(即直線路由的程式碼)位於CCmdTarget::OnCmdMsg之中,因此原始碼中的其他子類,最終都會呼叫CCmdTarget::OnCmdMsg函式。
4、以下類重寫了OnCmdMsg虛擬函式:CCmdTarget, CFrameWnd, CView, CDocument, CMDIFrameWnd, CPropertySheet, CDialog, COleDocument,注意CWnd類沒有重寫該虛擬函式。
5、原始碼中的OnCommand相當於模仿程式示例3.11中的g1函式,而OncmdMsg相當於是g函式。
6、假設訊息從CFrameWnd進入,即假設CWnd::OnWndMsg函式中的this指標指向的型別是CFrameWnd,則在其中呼叫的OnCommand,就是CFrameWnd::OnCommand。現在從此處開始列出所有有關的原始碼。

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{	......
	if (message == WM_COMMAND)     
	{	if (OnCommand(wParam, lParam))   //❶、假設this指標指向的型別是CFrameWnd
......}
......}

BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)  //winfrm.cpp
{	......
			return CWnd::OnCommand(wParam, lParam); }
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)    (wincore.cpp)
{	......
	return OnCmdMsg(nID,nCode,NULL,NULL);}  //❷、此處再次假設this指標指向的型別是CFrameWnd

/*❸、OnCmdMsg用於處理訊息在繼承子樹中的直線路由,該函式的具體程式碼(此處指用於實現直線路由的程式碼)位於頂級父類CCmdTarget之中,因此其他各子類最終都會呼叫CCmdTarget::OnCmdMsg函式。*/
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,   //winfrm.cpp
	AFX_CMDHANDLERINFO* pHandlerInfo)
{	CPushRoutingFrame push(this);
/*❹、 CFrameWnd類中,訊息橫向流動的三條路徑,這三條路徑最終都會呼叫CCmdTarget::OnCmdMsg函式,以便處理訊息在各自的繼承子樹中的直線路由,因為分路徑比較多,本示例只追蹤部分分路徑原始碼,其餘分路徑原理都是類似的,請讀者自行追蹤,但最終都會呼叫CCmdTarget::OnCmdMsg。
注意:CWnd未重寫OnCmdMsg函式,因此CWnd::OnCmdMsg就是直接呼叫CCmdTarget::OnCmdMsg。*/
CView* pView = GetActiveView();
	if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))	//路徑1
return TRUE;
	if (CWnd::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))//路徑2,直接呼叫CCmdTarget::OnCmdMsg
return TRUE;  
	
CWinApp* pApp = AfxGetApp();
	if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) //路徑3
return TRUE;
	return FALSE; }

//❺、CFrameWnd路徑1:CView::OnCmdMsg原始碼(viewcore.cpp),本路徑又產生兩個分路徑1---1和1---2
BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{	//分路徑1---1,直接呼叫CCmdTarget::OnCmdMsg
if(CWnd::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))return TRUE;
	if (m_pDocument != NULL){	CPushRoutingView push(this);
		return m_pDocument->OnCmdMsg(nID,nCode,pExtra, pHandlerInfo);} //分路徑1----2
	return FALSE;	}

//❻、CFrameWnd分路徑1----2:CDocument::OnCmdMsg原始碼(doccore.cpp)
//本路徑再次產生兩個分路徑1----2-----1和1----2------2
BOOL CDocument::OnCmdMsg(UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{	if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  //分路徑1----2-----1
return TRUE;
	if (m_pDocTemplate != NULL && 
m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))//分路徑1----2-----2
			return TRUE;
	return FALSE;}

//❼、CCmdTarget::OnCmdMsg原始碼,用於處理訊息在各自的繼承子樹中的直線路由,該程式中的執行原理與CWnd::OnWndMsg是類似的,詳見直線路由模仿程式示例3.10。
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,   //cmdtarg.cpp
	AFX_CMDHANDLERINFO* pHandlerInfo)
{	......
	const AFX_MSGMAP* pMessageMap;
	const AFX_MSGMAP_ENTRY* lpEntry;
......
	if (nCode != CN_UPDATE_COMMAND_UI){	nMsg = HIWORD(nCode);	nCode = LOWORD(nCode);	}
	// for backward compatibility HIWORD(nCode)==0 is WM_COMMAND
	if (nMsg == 0)	nMsg = WM_COMMAND;
// look through message map to see if it applies to us
//❽、以下for語句與MFC原始碼的直線路由程式碼的原理是相同的,詳見直線路由模仿程式示例3.10。
	for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;
	  pMessageMap = (*pMessageMap->pfnGetBaseMap)())
	{	// Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)!
		ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
/*❾、AfxFindMessageEntry函式的原始碼詳見MFC原始碼直線路由,該函式的功能是用於查詢訊息對映中的所有<訊息,處理函式>對。類似於模仿程式的while迴圈。*/
		lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
		if (lpEntry != NULL)
		{  ......
return _AfxDispatchCmdMsg(  /*❿、此函式的功能就是MFC原始碼直線路由goto語句跳轉到的類似swtich的功能,主要用於對比應該使用哪一個函式原型呼叫訊息處理函式。*/
this, nID, nCode, lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
		}	}
	return FALSE; }  // not handled

AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget,UINT nID, int nCode, //cmdtarg.cpp
	AFX_PMSG pfn, void* pExtra, UINT_PTR nSig, AFX_CMDHANDLERINFO* pHandlerInfo)
{	ENSURE_VALID(pTarget);
	UNUSED(nCode);   // unused in release builds
	union MessageMapFunctions mmf;
	mmf.pfn = pfn;
	BOOL bResult = TRUE; // default is ok
	if (pHandlerInfo != NULL)
		{	pHandlerInfo->pTarget = pTarget;	pHandlerInfo->pmf = mmf.pfn;	return TRUE;	}

	switch (nSig)
	{default:    // illegal
		ASSERT(FALSE);
		return 0;
		break;
	case AfxSigCmd_v:
		// normal command or control notification
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		(pTarget->*mmf.pfnCmd_v_v)();
		break;
......}   //一系列類似的case子句。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述
本文作者:黃邦勇帥(原名:黃勇)