MFC窗口的创建过程详细解析

来源:互联网 发布:关于网络推广的书籍 编辑:程序博客网 时间:2024/05/21 20:22

MFC窗口的创建过程详细解析

                                                                      作者:www.gomensoft.com

关于MFC的窗口一直感神秘,那么我来看下MFC中的窗口到底是一个什么(窗口前的窗口就直接忽略了,与Win32窗口大同小异)MFC中窗口主要涉及三个重要的函数,分CWnd::CreateEx(或者CWnd::Create)、AfxHookWindowCreateAfxCbtFilterHook函数,首先是大概介MFC的窗口,当CWnd::CreateExCWnd::CreateExAPI函数::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).CMDIFrameWnd的派生类(MFC自动生成的CMainFrame)中重载CMDIFrameWnd::CreateClient函数,如在CMainFrame重载如下:

 

BOOL CMainFrame::CreateClient(LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu)

{

AfxHookWindowCreate(&m_wndMDIClientWnd);

 BOOL bSucess = CMDIFrameWnd::CreateClient(lpCreateStruct,pWindowMenu);

if( !AfxUnhookWindowCreate() )

{

PostNcDestroy();

}

return bSucess;

}

  

   (3)CMDIClientWnd中添加各种消息映射函数就可以方便的操作MDI客户区窗口了。

 

 

 

原创文章,转载请注明出处:www.gomensoft.com