所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。 那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发? MFC窗口过程的指定 从前面的讨论可知,每一个“窗口类”都有自己的窗口过程。正常情况下使用该“窗口类”创建的窗口都使用它的窗口过程。 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消息的接收和处理 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处理(后面5.3.3.7节会解释它的处理),再继续下面的处理。 如果是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; }

评论