MFC視窗的建立過程詳細解析
MFC視窗的建立過程詳細解析
關於MFC的視窗創建過程一直感覺比較神祕,那麼我們來看下MFC中的視窗創建到底是一個什麼過程(視窗創建前的視窗類準備就直接忽略了,與標準Win32視窗類準備大同小異)。MFC中視窗創建主要涉及三個重要的函式,分別是CWnd::CreateEx(或者CWnd::Create)、AfxHookWindowCreate、AfxCbtFilterHook函式,首先是大概介紹下MFC的視窗創建過程,當CWnd::CreateEx被調用時,CWnd::CreateEx在調用API函式::CreateWindowEx創建視窗前會通過調用AfxHookWindowCreate安裝一個名
1. CWnd::CreateEx
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)
{
// allow modification of several common create parameters
準備一個結構給PreCreateWindow函式使用,這樣就允許應用程式在建立視窗前修改視窗建立引數,比如給
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
//允許應用程式修改視窗建立的引數。
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
Hook視窗的建立過程:主要是給當前現成安裝一個名為_AfxCbtFilterHook的執行緒鉤子,並講需要建立的視窗的CWnd指標儲存到執行緒狀態結構(_AFX_THREAD_STATE)中。
AfxHookWindowCreate(this);
開始建立視窗,在該函式正真開始建立視窗之前,AfxCbtFilterHook會被呼叫,AfxCbtFilterHook會執行子類化操作,把要建立的視窗的視窗過程子類化為執行緒狀態結構中的視窗過程(AfxGetModuleState()->m_pfnAfxWndProc),這樣MFC的視窗(CWnd及其派生類)都可以接收和響應包括從建立開始的各種各樣的訊息
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);
#ifdef _DEBUG
if (hWnd == NULL)
{
TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n",
GetLastError());
}
#endif
解除建立視窗的Hook
if (!AfxUnhookWindowCreate())
PostNcDestroy();// cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}
2.AfxHookWindowCreate
AfxHookWindowCreate主要是Hook視窗的建立過程:主要是給當前現成安裝一個名為_AfxCbtFilterHook的執行緒鉤子,並講需要建立的視窗的CWnd指標儲存到執行緒狀態結構
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
//獲取執行緒狀態
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
//如果執行緒狀態裡的m_pWndInit成員與pWnd相等表明給視窗正在建立中,直接返回
if (pThreadState->m_pWndInit == pWnd)
return;
if (pThreadState->m_hHookOldCbtFilter == NULL)
{
給本執行緒安裝一個名為AfxCbtFilterHook的鉤子,AfxCbtFilterHook會在::CreateWindowEx真正開始建立視窗前被呼叫,更多資訊請參考MSDN關於SetWindowsHookEx的解釋。
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
把需要Hook建立的視窗指標儲存到執行緒狀態中,AfxCbtFilterHook被呼叫時會用到。
pThreadState->m_pWndInit = pWnd;
}
3. AfxCbtFilterHook
AfxCbtFilterHook會執行子類化操作,把要建立的視窗的視窗過程子類化為執行緒狀態結構中的視窗過程(AfxGetModuleState()->m_pfnAfxWndProc),即MFC的標準視窗過程,這樣MFC的視窗(CWnd及其派生類)都可以接收和響應包括從建立開始的各種各樣的訊息
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 (HIWORD(lpcs->lpszClass))
{
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 (lstrcmpi(pszClassName, _T("ime")) == 0)
goto lCallNextHook;
}
ASSERT(wParam != NULL); // should be non-NULL HWND
//獲取即將建立的視窗控制代碼
HWND hWnd = (HWND)wParam;
WNDPROC oldWndProc;
if (pWndInit != NULL)
{
#ifdef _AFXDLL
AFX_MANAGE_STATE(pWndInit->m_pModuleState);
#endif
//確保視窗控制代碼未與視窗(CWnd)關聯
// the window should not be in the permanent map at this time
ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
//使視窗控制代碼與視窗(CWnd)關聯,
// connect the HWND to pWndInit...
pWndInit->Attach(hWnd);
//CWnd子類可以過載這個函式以在子類化進行前做一些處理
// allow other subclassing to occur first
pWndInit->PreSubclassWindow();
//獲取視窗的原始視窗過程,該視窗過程儲存在CWnd:: m_pfnSuper裡,用CWnd::GetSuperWndProcAddr()函式可以獲取,該函式為虛擬函式,可以過載把原來的視窗過程替換。
WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
ASSERT(pOldWndProc != NULL);
#ifndef _AFX_NO_CTL3D_SUPPORT
_AFX_CTL3D_STATE* pCtl3dState;
DWORD dwFlags;
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)
{
// subclass the window with standard AfxWndProc
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,
(DWORD)afxWndProc);
ASSERT(oldWndProc != NULL);
*pOldWndProc = oldWndProc;
}
}
else
#endif
{
// 通過AfxGetAfxWndProc()獲取MFC的標準視窗過程,該視窗過程儲存線上程狀態的m_pfnAfxWndProc成員中
// subclass the window with standard AfxWndProc
WNDPROC afxWndProc = AfxGetAfxWndProc();
//子類化視窗,即把要建立的視窗的視窗過程設為標準的MFC視窗過程,這樣標準MFC視窗過程就會通過MFC的訊息對映給視窗(CWnd)傳送各種訊息了。
oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,
(DWORD)afxWndProc);
ASSERT(oldWndProc != NULL);
//儲存子類化前的視窗過程,儲存在CWnd:: m_pfnSuper成員中
if (oldWndProc != afxWndProc)
*pOldWndProc = oldWndProc;
}
//結束視窗建立的Hook
pThreadState->m_pWndInit = NULL;
}
else
{
ASSERT(!bContextIsDLL);// should never get here
// subclass the window with the proc which does gray backgrounds
oldWndProc = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL)
{
SetProp(hWnd, _afxOldWndProc, oldWndProc);
if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc)
{
GlobalAddAtom(_afxOldWndProc);
SetWindowLong(hWnd, GWL_WNDPROC,
(DWORD)(pThreadState->m_bDlgCreate ?
_AfxGrayBackgroundWndProc :_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;
}
5.結語
從上述解析可以看出,MFC視窗建立過程並不複雜,主要就是:
1.在視窗建立前把視窗控制代碼和視窗(CWnd)關聯
2.通過鉤子技術在視窗建立前把視窗過程替換為MFC的標準視窗過程以利用訊息對映機制。
最後,我來介紹下利用MFC的這種視窗建立機制的一個應用:
我們知道,在一個普通的MFC的MDI視窗應用程式中,一般都包括CMDIFrameWnd(MDI框架視窗)及CMDIChildWnd(MDI子視窗),其實,MDI框架視窗還有一個MDI客戶視窗,該視窗是MDI子視窗的直接父視窗,這個MDI客戶視窗並沒有相應的視窗類(CWnd)來表現,這個MDI客戶視窗的控制代碼儲存在CMDIFrameWnd::m_hWndMDIClient成員裡,在CMDIFrameWnd被建立時,CMDIFrameWnd::CreateClient會被呼叫以建立這個MDI客戶視窗,CMDIFrameWnd::CreateClient建立這個MDI客戶視窗時是直接使用API函式建立的,其視窗控制代碼就儲存在m_hWndMDIClient中,這樣就給我們操作這個MDI客戶視窗帶來了不便。下面我們來看下如何通過CWnd來操作這個MDI客戶視窗。
(1).從CWnd派生一個類,如 CMDIClientWnd : public CWnd
(2).<