所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。
那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发?
从前面的讨论可知,每一个“窗口类”都有自己的窗口过程。正常情况下使用该“窗口类”创建的窗口都使用它的窗口过程。
MFC的窗口对象在创建HWND窗口时,也使用了已经注册的“窗口类”,这些“窗口类”或者使用应用程序提供的窗口过程,或者使用Windows提供的窗口过程(例如Windows控制窗口、对话框等)。那么,为什么说MFC创建的所有HWND窗口使用同一个窗口过程呢?
在MFC中,的确所有的窗口都使用同一个窗口过程:AfxWndProc或AfxWndProcBase(如果定义了_AFXDLL)。它们的原型如下:
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK
AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
这两个函数的原型都如符合windows的窗口过程函数原型。
如果动态链接到MFC DLL(定义了_AFXDLL),则AfxWndProcBase被用作窗口过程,否则AfxWndProc被用作窗口过程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE设置正确的模块状态,然后调用AfxWndProc。
下面,假设不使用MFC DLL,讨论MFC如何使用AfxWndProc取代各个窗口的原窗口过程。
窗口过程的取代发生在窗口创建的过程时,使用了子类化(Subclass)的方法。所以,从窗口的创建过程来考察取代过程。从前面可以知道,窗口创建最终是通过调用CWnd::CreateEx函数完成的,分析该函数的流程:
CREATESTRUCT结构类型的变量cs包含了传递给窗口过程的初始化参数。CREATESTRUCT结构描述了创建窗口所需要的信息,定义如下:
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams; //用来创建窗口的数据
HANDLE hInstance; //创建窗口的实例
HMENU hMenu; //窗口菜单
HWND hwndParent; //父窗口
int cy; //高度
int cx; //宽度
int y; //原点Y坐标
int x;//原点X坐标
LONG style; //窗口风格
LPCSTR lpszName; //窗口名
LPCSTR lpszClass; //窗口类
DWORD dwExStyle; //窗口扩展风格
} CREATESTRUCT;
cs表示的创建参数可以在创建窗口之前被程序员修改,程序员可以覆盖当前窗口类的虚拟成员函数PreCreateWindow,通过该函数来修改cs的style域,改变窗口风格。这里cs的主要作用是保存创建窗口的各种信息,::CreateWindowEx函数使用cs的各个域作为参数来创建窗口。
在创建窗口之前,创建了一个WH_CBT类型的钩子(Hook)。这样,创建窗口时所有的消息都会被钩子过程函数_AfxCbtFilterHook截获。
AfxCbtFilterHook函数首先检查是不是希望处理的Hook──HCBT_CREATEWND。如果是,则先把MFC窗口对象(该对象必须已经创建了)和刚刚创建的Windows窗口对象捆绑在一起,建立它们之间的映射(见后面模块-线程状态);然后,调用::SetWindowLong设置窗口过程为AfxWndProc,并保存原窗口过程在窗口类成员变量m_pfnSuper中,这样形成一个窗口过程链。需要的时候,原窗口过程地址可以通过窗口类成员函数GetSuperWndProcAddr得到。
这样,AfxWndProc就成为CWnd或其派生类的窗口过程。不论队列消息,还是非队列消息,都送到AfxWndProc窗口过程来处理(如果使用MFC DLL,则AfxWndProcBase被调用,然后是AfxWndProc)。经过消息分发之后没有被处理的消息,将送给原窗口过程处理。
最后,有一点可能需要解释:为什么不直接指定窗口过程为AfxWndProc,而要这么大费周折呢?这是因为原窗口过程(“窗口类”指定的窗口过程)常常是必要的,是不可缺少的。
接下来,讨论AfxWndProc窗口过程如何使用消息映射数据实现消息映射。Windows消息和命令消息的处理不一样,前者没有消息分发的过程,以下仅讨论Windows消息。
Windows消息送给AfxWndProc窗口过程之后,AfxWndProc得到HWND窗口对应的MFC窗口对象,然后,搜索该MFC窗口对象和其基类的消息映射数组,判定它们是否处理当前消息,如果是则调用对应的消息处理函数,否则,进行缺省处理。
下面,以一个应用程序的视窗口创建时,对WM_CREATE消息的处理为例,详细地讨论Windows消息的分发过程。
假如类CTview要处理WM_CREATE消息,使用ClassWizard加入消息处理函数CTview::OnCreate。下面,看这个函数怎么被调用:
视窗口最终调用::CreateEx函数来创建。由Windows系统发送WM_CREATE消息给视的窗口过程AfxWndProc,参数1是创建的视窗口的句柄,参数2是消息ID(WM_CREATE),参数3、4是消息参数。根据处理过程得出判断。例如,“CWnd::WindowProc”表示CWnd类的虚拟函数WindowProc被调用,并不一定当前对象是CWnd类的实例,事实上,它是CWnd派生类CTview类的实例;而“CTview::OnCreate”表示CTview的消息处理函数OnCreate被调用。下面描述每一步的详细处理。
首先,分析AfxWndProc窗口过程函数。
AfxWndProc的原型如下:
LRESULT AfxWndProc(HWND hWnd,
UINT nMsg, WPARAM wParam, LPARAM lParam)
如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(该消息被MFC内部用来确认窗口过程是否使用AfxWndProc),则从hWnd得到对应的MFC Windows对象(该对象必须已存在,是永久性<Permanent>对象)指针pWnd。pWnd所指的MFC窗口对象将负责完成消息的处理。这里,pWnd所指示的对象是MFC视窗口对象,即CTview对象。
然后,把pWnd和AfxWndProc接受的四个参数传递给函数AfxCallWndProc执行。
AfxCallWndProc原型如下:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd,
UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)
MFC使用AfxCallWndProc函数把消息送给CWnd类或其派生类的对象。该函数主要是把消息和消息参数(nMsg、wParam、lParam)传递给MFC窗口对象的成员函数WindowProc(pWnd->WindowProc)作进一步处理。如果是WM_INITDIALOG消息,则在调用WindowProc前后要作一些处理。
WindowProc的函数原型如下:
LRESULT CWnd::WindowProc(UINT message,
WPARAM wParam, LPARAM lParam)
这是一个虚拟函数,程序员可以在CWnd的派生类中覆盖它,改变MFC分发消息的方式。例如,MFC的CControlBar就覆盖了WindowProc,对某些消息作了自己的特别处理,其他消息处理由基类的WindowProc函数完成。
但是在当前例子中,当前对象的类CTview没有覆盖该函数,所以CWnd的WindowProc被调用。
这个函数把下一步的工作交给OnWndMsg函数来处理。如果OnWndMsg没有处理,则交给DefWindowProc来处理。
OnWndMsg和DefWindowProc都是CWnd类的虚拟函数。
OnWndMsg的原型如下:
BOOL CWnd::OnWndMsg( UINT message,
WPARAM wParam, LPARAM lParam,RESULT*pResult );
该函数是虚拟函数。
和WindowProc一样,由于当前对象的类CTview没有覆盖该函数,所以CWnd的OnWndMsg被调用。
在CWnd中,MFC使用OnWndMsg来分别处理各类消息:
如果是WM_COMMAND消息,交给OnCommand处理;然后返回。
如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。
如果是WM_ACTIVATE消息,先交给_AfxHandleActivate处理(后面
如果是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理;然后返回。
如果是其他的Windows消息(包括WM_ACTIVATE),则
首先在消息缓冲池进行消息匹配,
若匹配成功,则调用相应的消息处理函数;
若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否处理当前消息。这里,消息目标即CTview对象。
如果消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;
否则,该消息没有被应用程序处理,OnWndMsg返回FALSE。
附上消息处理的主要源码:
//在此函数中进行窗口过程的取代
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);
}
ASSERT(lParam != NULL);
LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;
ASSERT(lpcs != NULL);
CWnd* pWndInit = pThreadState->m_pWndInit;
BOOL bContextIsDLL = afxContextIsDLL;
if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL))
{
// Note: special check to avoid subclassing the IME window
if (_afxDBCS)
{
// check for cheap CS_IME style first...
if (GetClassLong((HWND)wParam, GCL_STYLE) & CS_IME)
goto lCallNextHook;
// get class name of the window that is being created
LPCTSTR pszClassName;
TCHAR szClassName[_countof("ime")+1];
if (DWORD_PTR(lpcs->lpszClass) > 0xffff)
{
pszClassName = lpcs->lpszClass;
}
else
{
szClassName[0] = '\0';
GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName));
pszClassName = szClassName;
}
// a little more expensive to test this way, but necessary...
if (::AfxInvariantStrICmp(pszClassName, _T("ime")) == 0)
goto lCallNextHook;
}
ASSERT(wParam != NULL); // should be non-NULL HWND
HWND hWnd = (HWND)wParam;
WNDPROC oldWndProc;
if (pWndInit != NULL)
{
AFX_MANAGE_STATE(pWndInit->m_pModuleState);
// the window should not be in the permanent map at this time
ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
// connect the HWND to pWndInit...
pWndInit->Attach(hWnd);
// allow other subclassing to occur first
pWndInit->PreSubclassWindow();
WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
ASSERT(pOldWndProc != NULL);
// subclass the window with standard AfxWndProc
WNDPROC afxWndProc = AfxGetAfxWndProc();
//以下这句即发生取代
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
(DWORD_PTR)afxWndProc);
ASSERT(oldWndProc != NULL);
if (oldWndProc != afxWndProc)
*pOldWndProc = oldWndProc;
pThreadState->m_pWndInit = NULL;
}
else
{
ASSERT(!bContextIsDLL); // should never get here
static ATOM s_atomMenu = 0;
bool bSubclass = true;
if (s_atomMenu == 0)
{
WNDCLASSEX wc;
memset(&wc, 0, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
s_atomMenu = (ATOM)::AfxCtxGetClassInfoEx(NULL, _T("#32768"), &wc);
}
// Do not subclass menus.
if (s_atomMenu != 0)
{
ATOM atomWnd = (ATOM)::GetClassLongPtr(hWnd, GCW_ATOM);
if (atomWnd == s_atomMenu)
bSubclass = false;
}
else
{
TCHAR szClassName[256];
if (::GetClassName(hWnd, szClassName, 256))
{
szClassName[255] = NULL;
if (_tcscmp(szClassName, _T("#32768")) == 0)
bSubclass = false;
}
}
if (bSubclass)
{
// subclass the window with the proc which does gray backgrounds
oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL)
{
SetProp(hWnd, _afxOldWndProc, oldWndProc);
if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc)
{
GlobalAddAtom(_afxOldWndProc);
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)_AfxActivationWndProc);
ASSERT(oldWndProc != NULL);
}
}
}
}
}
lCallNextHook:
LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
wParam, lParam);
#ifndef _AFXDLL
if (bContextIsDLL)
{
::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);
pThreadState->m_hHookOldCbtFilter = NULL;
}
#endif
return lResult;
}
/////////////////////////////////////////////////////////////////////////////
// The WndProc for all CWnd's and derived classes
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);
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);
}
/////////////////////////////////////////////////////////////////////////////
// Official way to send message to a CWnd
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
MSG oldState = pThreadState->m_lastSentMsg; // save for nesting
pThreadState->m_lastSentMsg.hwnd = hWnd;
pThreadState->m_lastSentMsg.message = nMsg;
pThreadState->m_lastSentMsg.wParam = wParam;
pThreadState->m_lastSentMsg.lParam = lParam;
#ifdef _DEBUG
_AfxTraceMsg(_T("WndProc"), &pThreadState->m_lastSentMsg);
#endif
// Catch exceptions thrown outside the scope of a callback
// in debug builds and warn the user.
LRESULT lResult;
TRY
{
#ifndef _AFX_NO_OCC_SUPPORT
// special case for WM_DESTROY
if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))
pWnd->m_pCtrlCont->OnUIActivate(NULL);
#endif
// special case for WM_INITDIALOG
CRect rectOld;
DWORD dwStyle = 0;
if (nMsg == WM_INITDIALOG)
_AfxPreInitDialog(pWnd, &rectOld, &dwStyle);
// delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
// more special case for WM_INITDIALOG
if (nMsg == WM_INITDIALOG)
_AfxPostInitDialog(pWnd, rectOld, dwStyle);
}
CATCH_ALL(e)
{
lResult = AfxProcessWndProcException(e, &pThreadState->m_lastSentMsg);
TRACE(traceAppMsg, 0, "Warning: Uncaught exception in WindowProc (returning %ld).\n",
lResult);
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
pThreadState->m_lastSentMsg = oldState;
return lResult;
}
/////////////////////////////////////////////////////////////////////////////
// main WindowProc implementation
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;
}
评论