MFC消息流动的内部实现

17 篇文章 1 订阅
9 篇文章 6 订阅

消息的一生神秘而漫长,期间曲折多为人所不知。今天就让我们一起“撩”一下这位高冷的神秘妹纸吧。

事实上,MFC中利用hook技术,把看似无关的操作关联了起来。所谓hook(钩子)是Windows中一种高级的编程技术,它可以保证,在特定情况发生的时候就转去执行我们所指定的操作(是一种霸道机关术)。

MFC的hook发生在CWnd派生类对象的产生之际。在WINCORE.CPP中我们可以看到如下的代码:

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
	LPCTSTR lpszWindowName, DWORD dwStyle,
	int x, int y, int nWidth, int nHeight,
	HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
……
    AfxHookWindowCreate(this);//关键操作
	HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
			cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
			cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
……
}

在WINCORE.CPP中又有:

voidAFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
……
      if (pThreadState->m_hHookOldCbtFilter== NULL)
    {
       pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
           _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());//关键操作
       if (pThreadState->m_hHookOldCbtFilter== NULL)
           AfxThrowMemoryException();
    }
……
}

WH_CBT是hook类型中的一种,它意味着安装一个Computer-Based- Training(CBT)滤网函数。安装后,Windows系统在进行一下任何一个操作之前都会先调用我们的滤网函数:

◎激活一个窗口(HCBT_ACTIVATE)

◎产生或摧毁一个窗口(HCBT_CREATEWND、HCBT_DESTROYWND)

◎最大化或最小化一个窗口(HCBT_MINMAX)

◎移动或缩放一个窗口(HCBT_MOVESIZE)

◎完成一个来自系统菜单的系统命令(HCBT_SYSTEMCOMMAND)

◎从系统队列中移去一个鼠标或键盘消息(HCBT_KEYSKIPPED、HCBT_CLICKSKIPPED)

因此,在上述hook安装之后,任何窗口产生之前,滤网函数_AfxCbtFilterHook都会先被调用(WINCORE.CPP):

LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
	_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
	if (code != HCBT_CREATEWND)
	{
		// wait for HCBT_CREATEWND just pass others on...
		return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
			wParam, lParam);
	}
……
    if (!afxData.bWin4 && !bContextIsDLL &&
(pCtl3dState = _afxCtl3dState.GetDataNA()) != NULL &&
pCtl3dState->m_pfnSubclassDlgEx != NULL &&
(dwFlags = AfxCallWndProc(pWndInit, hWnd, WM_QUERY3DCONTROLS)) != 0)
	{
		// was the class registered with AfxWndProc?
		WNDPROC afxWndProc = AfxGetAfxWndProc();
		BOOL bAfxWndProc = ((WNDPROC)
		GetWindowLong(hWnd, GWL_WNDPROC) == afxWndProc);

		pCtl3dState->m_pfnSubclassDlgEx(hWnd, dwFlags);

		// subclass the window if not already wired to AfxWndProc
		if (!bAfxWndProc) //若窗口处理函数不是AfxWndProc则设置之
		{
			// subclass the window with standard AfxWndProc
			oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)afxWndProc);
			ASSERT(oldWndProc != NULL);
			*pOldWndProc = oldWndProc;
		}
	}
……
}

我们可以看到AfxGetAfxWndProc函数的操作如下(WINCORE.CPP):

WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL//为DLL时
	return AfxGetModuleState()->m_pfnAfxWndProc;
#else
	return &AfxWndProc;
#endif
}

由上可见,窗口类中所登记的窗口函数被一众函数强行设置为AfxWndProc。于是,当执行::DispatchMessage函数时,消息就被默默地送往AfxWndProc中去处理了。据说:这样迂回的做法是为了包容新的3D Controls,并与MFC2.5兼容。

好了,热身运动做完了,下面就来看看消息到底是怎么被处理的吧。我们已经知道,消息被抓取后将被送往AfxWndProc,该函数主要操作如下(WINCORE.CPP):

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);
	ASSERT(pWnd != NULL);
	ASSERT(pWnd->m_hWnd == hWnd);
	if (pWnd == NULL || pWnd->m_hWnd != hWnd)
		return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
	return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

其中AfxCallWndProc的主要操作如下(WINCORE.CPP):

LRESULTAFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
    WPARAM wParam = 0, LPARAM lParam = 0)
{
……  
 // delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam,lParam);
……
returnlResult;
}

其中的WindowProc函数如下(WINCORE.CPP):

LRESULTCWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

    // OnWndMsg does most of the work, exceptfor DefWindowProc call

    LRESULT lResult = 0;

    if (!OnWndMsg(message, wParam, lParam, &lResult))

       lResult = DefWindowProc(message, wParam, lParam);

    return lResult;

}

其中调用的DefWindowProc主要操作如下(WINCORE.CPP):

LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	if (m_pfnSuper != NULL)
		return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

	WNDPROC pfnWndProc;
	if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)
		return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);
	else
		return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);
}

WindowProc中还调用了OnWndMsg函数,该函数是分辨并处理消息的专职机构。如果是命令消息,就交给OnCommand处理,如果是通知消息,就交给OnNotify处理。WM_ACTIVATE和WM_SETCURSOR也都有特定的处理函数。而一般的Windows消息,就直接在消息映射表中上溯,寻找其处理程序。之所以要区分出命令消息WM_COMMAND和通知消息WM_NOTIFY,是因为它们的处理方式会更加地微妙(这个以后再说了)。

OnWndMsg的主要操作逻辑如下(WINCORE.CPP):

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
    // special case for commands
	if (message == WM_COMMAND)
	{
		if (OnCommand(wParam, lParam))
		{
			lResult = 1;
			goto LReturnTrue;
		}
		return FALSE;
	}
    // special case for notifies
	if (message == WM_NOTIFY)
	{
		NMHDR* pNMHDR = (NMHDR*)lParam;
		if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
			goto LReturnTrue;
		return FALSE;
	}
……
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
UINT iHash; iHash = (LOWORD((DWORD)pMessageMap) ^ message) & (iHashMax-1);
AfxLockGlobals(CRIT_WINMSGCACHE);
AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
const AFX_MSGMAP_ENTRY* lpEntry;
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)//检查该消息是否存在cache之中
	{
		// cache hit
		lpEntry = pMsgCache->lpEntry;
		AfxUnlockGlobals(CRIT_WINMSGCACHE);
		if (lpEntry == NULL)
			return FALSE;

		// cache hit, and it needs to be handled
		if (message < 0xC000)
			goto LDispatch;
		else
			goto LDispatchRegistered;
	}
else
	{
	// not in cache, look for it
	pMsgCache->nMsg = message;
	pMsgCache->pMessageMap = pMessageMap;

	for (/* pMessageMap already init'ed */; pMessageMap != NULL;
			pMessageMap = pMessageMap->pBaseMap)
	{
		// Note: catch not so common but fatal mistake!!
		//      BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
/*
利用AfxFindMessageEntry寻找消息映射表中对应的消息处理程序。如果找到,再按照message为一般消息(< 0xC000)或自行注册的消息(> 0xC000)分别跳转到LDispatch:或LDispatchRegistered:去执行
*/
		if (message < 0xC000)
		{
			// constant window message
			if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL)
			{
				pMsgCache->lpEntry = lpEntry;
				AfxUnlockGlobals(CRIT_WINMSGCACHE);
				goto LDispatch;
			}
		}
		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);
			// must be successfully registered
				if (*pnID == message)
				{
					pMsgCache->lpEntry = lpEntry;
						AfxUnlockGlobals(CRIT_WINMSGCACHE);
					goto LDispatchRegistered;
				}
				lpEntry++;      // keep looking past this one
			}
		}
	}

	pMsgCache->lpEntry = NULL;
	AfxUnlockGlobals(CRIT_WINMSGCACHE);
	return FALSE;
}
ASSERT(FALSE);      // not reached
LDispatch:
ASSERT(message < 0xC000);
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
int nSig;
nSig = lpEntry->nSig;
……
    switch (nSig)
{
	default:
		ASSERT(FALSE);
		break;

	case AfxSig_bD:
		lResult = (this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam));
		break;

	case AfxSig_bb:     // AfxSig_bb, AfxSig_bw, AfxSig_bh
		lResult = (this->*mmf.pfn_bb)((BOOL)wParam);
		break;

	case AfxSig_bWww:   // really AfxSig_bWiw
		lResult = (this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam),
			(short)LOWORD(lParam), HIWORD(lParam));
		break;
……
}
goto LReturnTrue;
LDispatchRegistered:    // for registered windows messages
	ASSERT(message >= 0xC000);
	mmf.pfn = lpEntry->pfn;
	lResult = (this->*mmf.pfn_lwl)(wParam, lParam);

LReturnTrue:
	if (pResult != NULL)
		*pResult = lResult;
	return TRUE;
}

其中调用的AfxFindMessageEntry函数主要操作如下(WINCORE.CPP):

const AFX_MSGMAP_ENTRY* AFXAPI
AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,
	UINT nMsg, UINT nCode, UINT nID)
{
#if defined(_M_IX86) && !defined(_AFX_PORTABLE)
// 32-bit Intel 386/486 version.
//此处使用内联汇编做相应的处理,从而加快速度
……
#else  // _AFX_PORTABLE
	// C version of search routine
	while (lpEntry->nSig != AfxSig_end)
	{
		if (lpEntry->nMessage == nMsg && lpEntry->nCode == nCode &&
			nID >= lpEntry->nID && nID <= lpEntry->nLastID)
		{
			return lpEntry;
		}
		lpEntry++;//AFX_MSGMAP_ENTRY指针的++操作
	}
	return NULL;    // not found
#endif  // _AFX_PORTABLE
}

由上面的函数逻辑可知,对于一般的消息,所进行的操作只是比较消息映射表,如果有吻合的项目,就调用表中所记录的函数。比较得对象主要有两个,优先在MFC中的cache中查找,没有命中的话,查找的第二个对象就是建立的消息映射表。比较成功后转到执行函数时,有一个庞大的switch/case结构,这里主要是为了保证类型安全。

假如,有WM_PAINT消息发生于CMyView窗口中,则消息的流动路线如下图所示:







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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值