VC++/MFC消息映射机制(3):MFC消息路由的源码分析

VC++/MFC消息映射机制(3):MFC消息路由的源码分析

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂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子句。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
本文作者:黄邦勇帅(原名:黄勇)

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值