1. 程式人生 > >MFC視窗的建立過程詳細解析

MFC視窗的建立過程詳細解析

MFC視窗的建立過程詳細解析

關於MFC的視窗一直感神祕,那麼我來看下MFC中的視窗到底是一個什麼(視窗前的視窗就直接忽略了,與Win32視窗大同小異)MFC中視窗主要涉及三個重要的函式,分CWnd::CreateEx(或者CWnd::Create)、AfxHookWindowCreateAfxCbtFilterHook函式,首先是大概介MFC的視窗,當CWnd::CreateEx調CWnd::CreateEx調API函式::CreateWindowEx視窗前會通過調AfxHookWindowCreate安裝一個名

_AfxCbtFilterHook子,並將需要建的視窗的CWnd儲存到程狀態結中,在API函式::CreateWindowEx真正視窗前AfxCbtFilterHook會被調AfxCbtFilterHook行子化操作,把要建的視窗的視窗程子為線程狀態結構中的視窗(AfxGetModuleState()->m_pfnAfxWndProc),即MFC視窗這樣MFC的視窗(CWnd及其派生)都可以通訊息對映機制接收和響包括從建開始的各種各的訊息(關於AfxCbtFilterHook可以參考MSDN中關於SetWindowsHookEx
的解
)

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函式使用,這樣就允許應用程式在建立視窗前修改視窗建立引數,比如給

cs.lpszClass重新指定一個視窗類給CreateWindow函式。

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的這種視窗建立機制的一個應用:

我們知道,在一個普通的MFCMDI視窗應用程式中,一般都包括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).<