zoukankan      html  css  js  c++  java
  • vc++窗口的创建过程(MFC消息机制的经典文章)

    一、什么是窗口类 
      在Windows中运行的程序,大多数都有一个或几个可以看得见的窗口,而在这些窗口被创建起来之前,操作系统怎么知道该怎样创建该窗口,以及用户操作该窗口的各种消息交给谁处理呢?所以VC在调用Windows的API(CreateWindow或者CreateWindowEx)创建窗口之前,要求程序员必须定义一个窗口类(不是传统C++意义上的类)来规定所创建该窗口所需要的各种信息,主要包括:窗口的消息处理函数、窗口的风格、图标、 鼠标、菜单等。其定义如下: 

    typedef struct tagWNDCLASSA(注:该结构为ANSII版本) 

    UINT    style ; 
    WNDPROC    lpfnWndProc ; 
    int    cbClsExtra ; 
    int    cbWndExtra ; 
    HINSTANCE  hInstance ; 
    HICON     hIcon ; 
    HCURSOR   hCursor ; 
    HBRUSH    hbrBackground ; 
    LPCSTR  lpszMenuName ; 
    LPCSTR  lpszClassName ; 
    }WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ; 

    style 表示该类窗口的风格,如style = CS_VREDRAW|CS_HREDRAW表示窗口在运动或者调整大小时需要重画,关于其它风格可在 MSDN中查到。 
    lpfnWndProc为一指针,指向用户定义的该窗口的消息处理函数。 
    cbClsExtra 用于在窗口类结构中保留一定空间,用于存在自己需要的某些信息。 
    cbWndExtra用于在Windows内部保存的窗口结构中保留一定空间。 
    hInstance 表示创建该窗口的程序的运行实体代号(WinMain的参数之一)。 
    hIcon、hCursor、hbrBackground、lpszMenuName分别表示该窗口的图标、鼠标形状、背景色以及菜单。 
    lpszClassName表示该窗口类别的名称,即标识该窗口类的标志。 
      从上面可以看出一个窗口类就对应一个WNDCLASSA结构(这里以ANSII为例),当程序员将该结构按自己要求填写完成后,就可以调用RegisterClass(或RegisterClassEx)函数将该类注册,这样以后凡是要创建该窗口,只需要以该类名(lpszClassName中指定)为参数调用CreateWindow,你看多方便呀,真是一举多得啊! 
        总结:但窗口结构注册(调用RegisterClass(或RegisterClassEx)函数)后,以后凡是要创建该窗口,只需要以该类名(lpszClassName中指定)为参数调用CreateWindow。
    二、传统SDK中的窗口类 
      既然我们知道了什么是窗口类,那我们就将它放到一个传统的SDK程序中,看看是怎样运行的。 #include <windows.h> 

    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 
              PSTR szCmdLine, int iCmdShow) 

    static TCHAR szAppName[] = TEXT ("HelloWin") ; 
    WNDCLAS wndclass ; 

    wndclass.style  = CS_HREDRAW | CS_VREDRAW ; 
    wndclass.lpfnWndProc = WndProc ; 
    wndclass.cbClsExtra   = 0 ; 
    wndclass.cbWndExtra = 0 ; 
    wndclass.hInstance   = hInstance ; 
    wndclass.hIcon  = LoadIcon (NULL, IDI_APPLICATION) ; 
      wndclass.hCursor  = LoadCursor (NULL, IDC_ARROW) ; 
    wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 
      wndclass.lpszMenuNam = NULL ; 
    wndclass.lpszClassName = szAppName ; 

    RegisterClass (&wndclass); 

    hwnd = CreateWindow( szAppName, // window class name 
    TEXT ("The Hello Program"), // window caption 
    WS_OVERLAPPEDWINDOW, // window style 
    CW_USEDEFAULT, // initial x position 
    CW_USEDEFAULT, // initial y position 
    CW_USEDEFAULT, // initial x size 
    CW_USEDEFAULT, // initial y size 
    NULL, // parent window handle 
      NULL,     // window menu handle 
      hInstance,   // program instance handle 
      NULL) ;   // creation parameters 
       
    ShowWindow (hwnd, iCmdShow) ; 
    UpdateWindow (hwnd) ; 
       
    while (GetMessage (&msg, NULL, 0, 0)) 
       { 
    TranslateMessage (&msg) ; 
      DispatchMessage (&msg) ; 
        } 
    return msg.wParam ; 


      这是一个标准的Windows程序代码,程序被启动后,填写一个窗口类,然后调用RegisterClass将该类注册,接着创建该窗口,最后显示窗口和进入消息循环。 

    三、MFC中的窗口类 
      当你看到这里,也许你可能会感到奇怪:我在用MFC向导做程序时,并没有进行什么窗口类的填写和注册吗?是的,你没有,但是向导帮你做了。在展示向导是怎么做的之前,请让我先介绍一下预先知识。 
      在MFC系统中定义了五个默认的窗口类(这里不包括AFX_WNDCOMMCTLS_REG),分别定义在AFXIMPL.h中:   #define AFX_WND_REG          (0x0001) 
      #define AFX_WNDCONTROLBAR_REG    (0x0002) 
      #define AFX_WNDMDIFRAME_REG      (0x0004) 
      #define AFX_WNDFRAMEORVIEW_REG    (0x0008) 
      #define AFX_WNDDOLECONTROL_REG    (0x0020) 

    在WINCORE.cpp定义了这些窗口类对应的字符串名称:  const TCHAR _afxWnd[] = AFX_WND; 
      const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR; 
      const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME; 
      const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW; 
      const TCHAR _afxWndOleControl[] = AFX_WNDOLERONTROL; 

    在AFXIMPL.h中定义了五个AFX_XXX对应的字符串:  #define AFX_WND         AFX_WNDCLASS("WND") 
      #define AFX_WNDCONTROLBAR  AFX_WNDCLASS("ControlBar") 
      #define AFX_WNDMDIFRAME   AFX_WNDCLASS("MDIFrame") 
      #define AFX_WNDFRAMEORVIEW AFX_WNDCLASS("FrameOrView") 
      #define AFX_WNDOLECONTROL  AFX_WNDCLASS("OleControl") 

      看到这里也许有些心急了,其实上面一堆代码只是定义了五个默认窗口类的字符串名称和二进制名称,具体注册行为在全局函数AfxDeferRegisterClass中: #define AfxDeferRegisterClass(fClass) / 
    ((afxRegisteredClasses & fClass) ? TRUE:AfxEndDeferRegisterClass(fClass) 
      #define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses 

      BOOL AFXAPI AfxEndDeferRegisterClass(short fClass) 
      { 
        WNDCLASS wndCls; 
        wndCls.lpfnWndProc = DefWindowProc; 
        if(fClass & AFX_WND_REG) 
        { 
          wndCls.lpszClassName=_afxWnd; 
          AfxRegisterClass(&wndCls); 
        }else if(fClass & AFX_WNDOLECONTROL_REG) 
        { 
          wndCls.lpszClassName=_afxWndOleControl; 
          AfxRegisterClass(&wndCls); 
        }else if(fClass & AFX_WNDCONTROLBAR_REG) 
        { 
          wndCls.lpszClassName=_afxWndControlBar; 
          AfxRegisterClass(&wndCls); 
        }else if(fClass & AFX_WNDMDIFRAME_REG) 
        { 
          RegisterWithIcon(&wndCls,_afxWndMDIFrame,AFX_IDI_MDIFRAME); 
        }else if(fClass & AFX_WNDFRAMEORVIEW_REG) 
        { 
      RegisterWithIcon(&wndCls,_afxWndFrameOrView,AFX_IDI_STD_FRAME); 
        }else if(fClass & AFX_WNDCOMMCTLS_REG) 
        { 
          InitCommonControls(); 
        } 
      } 
    从以上例子可以看出, AfxDeferRegisterClass函数用if/else结构实现各种不同窗口的注册,所所以MFC函数窗口注册的时候调用AfxDeferRegisterClass函数就可以了。
      从上面的代码可以看出,AfxDeferRegisterClass函数首先判断该窗口类是否注册,如已注册则直接返回,否则调用AfxEndDeferRegisterClass进行注册,即注册要求的默认窗口类。其中RegisterWithIcon和InitCommonControls最终也是转化为调用AfxRegisterClass,而AfxRegisterClass函数调用RegisterClass进行注册,啊,终于看到SDK中的RegisterClass了,看到它总有一种亲切感! 
      有了上面的知识,我们就可以很容易摸清MFC是怎样注册窗口类的了!我们知道Windows上所有看得见的东西,在MFC中都是继承于CWnd类的,而CWnd类创建窗口的成员函数是Create和CreateEx,由于Create最终是调用CreateEx,所以我们只需要看CreateEx函数就行了: create()-->CreateEx()??CREATESTRUCT
    ??  PreCreateWindow(cs);
    |?? AfxDeferRegisterClass(AFX_WND_REG) 
                              ?? CreateWindowEx()

     BOOL CWnd::CreateEx(DWORD dwExStyle, LPCSTSTR lpszClassName, 
                …… LPVOID lpParam) 
      { 
       CREATESTRUCT cs; 
       cs.dwExStyle = dwExStyle; 
       … … 
       cs.lpCreateParams = lpParam; 

       PreCreateWindow(cs); 
       AfxHookWindowCreate(this); 
       HWND hWnd=::CreateWindowEx(cs.dwStyle,cs.lpszClass,…,cs.lpCreateParams); 
       …… 
      } 

      啊,一看到CreateWindowEx,亲切感又来了,这不是和SDK中的CreateWindow一样嘛,是创建窗口!既然这样,那么注册窗口肯定在该函数之前,会是谁呢?如果你做过一点MFC程序,你就会对将眼光停留PreCreateWindow上。对!就是它了。 
      PreCreateWindow函数是CWnd类的一个虚拟函数,提供程序设置待创建窗口的属性(包括窗口类),这样继承于CWnd的类都可以按照自己的要求在PreCreateWindow中设置自己的属性了,而且我们看到MFC也是这样做的: BOOL CWnd::PreCreateWindow(CREATESTRUCT &cs) 

       if(cs.lpszClass = = NULL) 
       { 
         AfxDeferRegisterClass(AFX_WND_REG); 
         cs.lpszClass = _afxWnd; 
       } 
       return TRUE; 


    BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT &cs) 

       if(cs.lpszClass = = NULL) 
       { 
         AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG); 
        cs.lpszClass = _afxWndFrameOrView; 
       } 
       return TRUE; 


    BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT &cs) 

      if(cs.lpszClass = = NULL) 
      { 
       AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG); 
       cs.lpszClass = _afxWndMDIFrame; 
      } 


    BOOL CMDIChildWnd::PreCreateWindow(CREATESTRUCT &cs) 

       return CFrameWnd::PreCreateWindow(cs); 


    BOOL CView::PreCreateWindow(CREATESTRUCT &cs) 

       if(cs.lpszClass = = NULL) 
       { 
         AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG); 
         cs.lpszClass = _afxWndFrameOrView; 
       } 


    就是通过继承的方法,MFC实现常用类的窗口注册(代码并不完全,是从MFC中抽取对我们有意义的一部分代码)。 

    四、在MFC中注册自己的窗口类 
      在MFC中创建一个窗口,就必须是继承于CWnd类的,这样你的CMyWnd类自然就有了PreCreateWindow方法。你想注册有自己个性的窗口类,那么就在该函数中进行吧。也就是在PreCreateWindow函数中注册自己的窗口类,然后将窗口类的类名以及待创建窗口的其它属性(见CREATESTRUCT结构)填写cs,然后返回系统,供系统创建你的窗口。

    用SDK建立类的过程:
    填写一个窗口类,然后调用RegisterClass将该类注册,接着创建该窗口,最后显示窗口和进入消息循环。 
    用MFC建立窗口的过程:
    我们知道Windows上所有看得见的东西,在MFC中都是继承于CWnd类的,而CWnd类创建窗口的成员函数是Create和CreateEx,由于Create最终是调用CreateEx,所以我们只需要看CreateEx函数就行了: 
    create()-->CreateEx()??CREATESTRUCT
    ??PreCreateWindow(cs);
    |?? AfxDeferRegisterClass(AFX_WND_REG) 
                     ?? AfxHookWindowCreate(this); //为窗口关联一个消息处理函数WndProc()
                     ?? CreateWindowEx()
    **********************************************************************************************
    CWnd::CreateEX中HOOK函数作用

    VC   2009-08-26 20:25   阅读9   评论0   字号: 大大  中中  小小 用最基本的一句话概述,钩子函数起了很大作用。故事是这样的,有些漫长,也需要些耐心。

    MFC中消息分为3类:

     1. WM_COMMAND:所有的UI组件和加速键都会产生这种消息,所有派生于CCmdTarget的类都有能力处理该消息

     2. 标准消息:除WM_COMMAND之外的WM_xx消息都是标准消息,派生于CWnd的类都有能力处理该消息

     3. 控件通知消息:用于子窗口控件向父窗口发送的消息 

    在MFC的消息映射表的建立中,通过一组宏,你就可以让自己的类先于父类处理某些Windows消息,这种行为很像虚函数,只是我们重载的内容不是虚函数,而是消息。 

    推动消息的泵

    第一阶段 窗口过程
    在产生一个窗口的时候,会调用CFrameWnd::Create,所有的故事也都从这里展开。下面的代码为了简洁,去掉了不相关的代码
      BOOL CFrameWnd::Create(…)   {
         //  … 
           if  ( ! CreateEx(…))   {
             //  … 
         } 
         //  … 
     } 
    BOOL CWnd::CreateEx(…)   {
         //  … 
         AfxHookWindowCreate( this );
        HWND hWnd  =  ::CreateWindowEx(…);
         //  … 
     } 

      void  AFXAPI AfxHookWindowCreate(CWnd *  pWnd)   {
         //  … 
           if  (pThreadState -> m_hHookOldCbtFilter  ==  NULL)   {
            pThreadState -> m_hHookOldCbtFilter  =  ::SetWindowsHookEx(WH_CBT,
            _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
             //  … 
         } 
         //  … 
         pThreadState -> m_pWndInit  =  pWnd;


    这样,通过AfxHookWindowCreate,在当前线程中安装了一个钩子,用来拦截和窗口相关的事件,每当:
    1. 另一个窗口成为active;
    2. 产生或摧毁一个窗口
    3. Minimize或maximize一个窗口;
    4. 移动或缩放一个窗口;
    5. 完成一个来自系统菜单的命令;
    6. 从系统队列中取出一个消息;
    时,都会先调用_AfxCbtFilterHook(即每当有一个可能引发消息发生的事件的时候都会调用_AfxCbtFilterHook,然后这个函数对这些消息进行过滤,能够处理的就交给AfxGetAfxWndProc,不能处理的就交给全局的DefWndProc()函数),接下来看看钩子函数作了什么: 
     LRESULT CALLBACK
    _AfxCbtFilterHook( int  code, WPARAM wParam, LPARAM lParam)   {
         //  … 
         WNDPROC afxWndProc  =  AfxGetAfxWndProc();
        oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
         //  … 
     } 
     WNDPROC AFXAPI AfxGetAfxWndProc()   {
         //  … 
         return   & AfxWndProc;


    这样,_AfxCbtFilterHook的工作总结起来就是通过窗口子类化,把新建的窗口的窗口过程设置成AfxWndProc
    到这里,我们终于找到了窗口过程。
    结论
    CFrameWnd::Create创建窗口调用CWnd::CreateEx
    CWnd::CreateEx调用AfxHookWindowCreate准备为窗口设置钩子
    AfxHookWindowCreate调用::SetWindowHookEx为窗口设置了一个WH_CBT类型的钩子来过滤消息,并把过滤函数设置成_AfxCbtFilterHook
    _AfxCbtFilterHook通过窗口子类化设置窗口的窗口过程为AfxWndProc
    这样,通过::DispatchMessage发送给窗口的消息就会源源不断地送到AfxWndProc中来,可以想到,AfxWndProc利用MFC的消息映射表,分门别类的对消息进行分流。

    即每当有一个可能引发消息发生的事件的时候都会调用_AfxCbtFilterHook,然后这个函数对这些消息进行过滤,能够处理的就交给AfxGetAfxWndProc,不能处理的就交给全局的DefWndProc()函数
    OnNcCreate,当CWnd对象第一次被创建时,框架在WM_CREATE消息之前调用这个成员函数。可以修改CREATESTRUCT结构,PreCreateWindow也是可以修改CREATESTRUCT
      结构,他们有什么区别?   
      PreCreateWindow用的比较多,OnNcCreate都用在什么地方??

    OnNcCreate是响应WM_NCCREATE,   当窗口开始时先创建客户区,所以先发送WM_NCCREATE消息,   当非客户区都创建好了,再发送WM_CREATE,去创建窗口客户区,   
      The   WM_NCCREATE   message   is   sent   prior   to   the   WM_CREATE   message   when   a   window   is   first   created.     
        
      意思是说,WM_NCCREATE比WM_CREATE先发给窗口程序,在窗口一创建的时候   
      就是说:   
        
      0.   call   CreateWindow/CreateWindowEx开始   
      0.5   PreCreateWindow   <---   HOOK(窗口句柄无效)   
      1.   窗口创建   
      2.   WM_NCCREATE   (窗口句柄有效)   
      3.   WM_CREATE   (窗口句柄有效)   
      4.   call   CreateWindow/CreateWindowEx结束   
        
      这些很容易验证
    [转]CWnd中PreCreateWindow、PreSubclassWindow、SubclassWindow的区别 
    Posted on 2009-01-15 16:35 天之骄子 阅读(503) 评论(0)  编辑 收藏 引用  
    MFC(VC6.0)的CWnd及其子类中,有如下三个函数: 
     class CWnd : public CCmdTarget
     {
    public:
            virtual BOOL PreCreateWindow(CREATESTRUCT& cs);                             virtual void PreSubclassWindow();
       BOOL SubclassWindow(HWND hWnd);
           
    };
      让人很不容易区分,不知道它们究竟干了些什么,在什么情况下要改写哪个函数?
      想知道改写函数?让我先告诉你哪个不能改写,那就是SubclassWindow。Scott Meyers的杰作<<Effective C++>>的第36条是这样的Differentiate between inheritance of interface and inheritance of implementation. 看了后你马上就知道,父类中的非虚拟函数是设计成不被子类改写的。根据有无virtual关键字,我们在排除了SubclassWindow后,也就知道PreCreateWindow和PreSubClassWindow是被设计成可改写的。接着的问题便是该在什么时候该写了。要知道什么时候该写,必须知道函数是在什么时候被调用,还有执行函数的想要达到的目的。我们先看看对这三个函数,MSDN给的解释:
      PreCreateWindow:
      Called by the framework before the creation of the Windows window 
      attached to this CWnd object.
      (译:在窗口被创建并attach到this指针所指的CWnd对象之前,被framework调用)
      PreSubclassWindow:
      This member function is called by the framework to allow other necessary 
      subclassing to occur before the window is subclassed.
      (译:在window被subclassed之前被framework调用,用来允许其它必要的subclassing发生)
    虽然我已有译文,但还是让我对CWnd的attach和窗口的subclass作简单的解释吧!要理解attach,我们必须要知道一个C++的CWnd对象和窗口(window)的区别:window就是实在的窗口,而CWnd就是MFC用类对window所进行C++封装。attach,就是把窗口附加到CWnd对象上操作。附加(attach)完成后,CWnd对象才和窗口发生了联系。窗口的subclass是指修改窗口过程的操作,而不是面向对象中的派生子类。
      好了,PreCreateWindow由framework在窗口创建前被调用,函数名也说明了这一点,Pre应该是previous的缩写,PreSubclassWindow由framework在subclass窗口前调用。 这段话说了等于没说,你可能还是不知道,什么时候该改写哪个函数。罗罗嗦嗦的作者,还是用代码说话吧!源码之前,了无秘密(候捷语)。我们就看看MFC中的这三个函数都是这样实现的吧! 
     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
         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;
         }
         
         AfxHookWindowCreate(this);
         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);
             
             return TRUE;
     }
     
     // for child windows
    BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
    {
         if (cs.lpszClass == NULL)
        {
             // make sure the default window class is registered
             VERIFY(AfxDeferRegisterClass(AFX_WND_REG));
             
             // no WNDCLASS provided - use child window default
             ASSERT(cs.style & WS_CHILD);
             cs.lpszClass = _afxWnd;
         }
         return TRUE;
     }
      CWnd::CreateEx先设定cs(CREATESTRUCT),在调用真正的窗口创建函数::CreateWindowEx之前,调用了CWnd::PreCreateWindow函数,并把参数cs以引用的方式传递了进去。而CWnd的PreCreateWindow函数也只是给cs.lpszClass赋值而已。毕竟,窗口创建函数CWnd::CreateEx的诸多参数中,并没有哪个指定了所要创建窗口的窗口类,而这又是不可缺少的(请参考<<windows程序设计>>第三章)。所以当你需要修改窗口的大小、风格、窗口所属的窗口类等cs成员变量时,要改写PreCreateWindow函数。 
     // From VS Install PathVC98MFCSRCWINCORE.CPP
     BOOL CWnd::SubclassWindow(HWND hWnd)
    {
         if (!Attach(hWnd))
             return FALSE;
         
         // allow any other subclassing to occur
         PreSubclassWindow();
         
         // now hook into the AFX WndProc
         WNDPROC* lplpfn = GetSuperWndProcAddr();
         WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,
             (DWORD)AfxGetAfxWndProc());
         ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
         
         if (*lplpfn == NULL)
             *lplpfn = oldWndProc;   // the first control of that type created
     #ifdef _DEBUG
         else if (*lplpfn != oldWndProc)
    {
              
                 ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);
         }
     #endif
         
         return TRUE;
     }
     
     void CWnd::PreSubclassWindow()
    {
         // no default processing
     }
      CWnd::SubclassWindow先调用函数Attach(hWnd)让CWnd对象和hWnd所指的窗口发生关联。接着在用::SetWindowLong修改窗口过程(subclass)前,调用了PreSubclassWindow。CWnd::PreSubclassWindow则是什么都没有做。
      在CWnd的实现中,除了CWnd::SubclassWindow会调用PreSubclassWindow外,还有一处。上面所列函数CreateEx的代码,其中调用了一个AfxHookWindowCreate函数,见下面代码: 
     // From VS Install PathVC98MFCSRCWINCORE.CPP
     BOOL CWnd::CreateEx( )
       {
         // allow modification of several common create parameters
          
             
             if (!PreCreateWindow(cs))
                   {
                 PostNcDestroy();
                 return FALSE;
             }
             
             AfxHookWindowCreate(this); 
             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);
             
              
                 return TRUE;
     }
      接着察看AfxHookWindowCreate的代码: 
     
     // From VS Install PathVC98MFCSRCWINCORE.CPP
     void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
       {
          
             
             if (pThreadState->m_hHookOldCbtFilter == NULL)
                   {
                 pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
                     _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
                 if (pThreadState->m_hHookOldCbtFilter == NULL)
                     AfxThrowMemoryException();
             }
              
     }
     
      其主要作用的::SetWindowsHookEx函数用于设置一个挂钩函数(Hook函数)_AfxCbtFilterHook,每当Windows产生一个窗口时(还有许多其它类似,请参考<<深入浅出MFC>>第9章,563页),就会调用你设定的Hook函数。
      这样设定完成后,回到CWnd::CreateEx函数中,执行::CreateWindowEx进行窗口创建,窗口一产生,就会调用上面设定的Hook函数_AfxCbtFilterHook。而正是在_AfxCbtFilterHook中对函数PreSubclassWindow进行了第二次调用。见如下代码:
     // From VS Install PathVC98MFCSRCWINCORE.CPP
      /**//////////////////////////////////////////////////////////////////////////////
     // Window creation hooks
     
     LRESULT CALLBACK
     _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
       {
                 
              
             // connect the HWND to pWndInit 
             pWndInit->Attach(hWnd);
         // allow other subclassing to occur first
         pWndInit->PreSubclassWindow();
          
               {
             // subclass the window with standard AfxWndProc
             oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);
             ASSERT(oldWndProc != NULL);
             *pOldWndProc = oldWndProc;
         }
          
     }
      也在调用函数SetWindowLong进行窗口subclass前调用了PreSubclassWindow.
    通常情况下窗口是由用户创建的 
    CWnd::Create(..) 
    ●在此流程中,MFC提供一个机会"PreCreateWindow()供用户在创建前作点手脚 

    而对于对话框等,窗口是通过subclass方式交给用户的 
    系统读入对话框模板,建立其中各个子窗口 

    然后将各子窗口的 消息处理函数替换成 对应的C++对象 的消息处理函数 (Subclass:子类化,或"接管") ,然后,这个子窗口就会按类中定义的方式来动作了。 

    在此过程中,调用的是CWnd:SubclassWindow( HWND hWnd ); 
    ●在此流程中,MFC提供一个机会"PreSubclassWindow" 供用户在关联前作点手脚 

    具体来说,如果你定义一个窗口(如CButton派生类CMyButton),然后使用对话框数据交换将一个按钮与自己的派生类对象关联,这时候,一些"建立前"的处理就应该写在"PreSubclassWindow"中。 

    如果你用的不是"对话框数据关联",而是在OnInitDialg中自己创建m_mybtn.Create(...) 
    这时候,一些"建立前"的处理就应该写在 
    "PreCreateWindow"中。
    这里“建立前”的处理包括像那些处理,跟PreCreateWindows()做的一些窗口初始化的工作有什么不同?
    PreCreateWindows函数中没有窗口可以用——还没有创建 
    PreSubclassWindow函数中可以对窗口进行操作。
    ******************************
    这些在窗口创建之初就加入了钩子,能否截获这些钩子。

     

     

    ------------------以下内容是对上面内容的具体解释,两部分必须结合着看--------------------------------------

    MFC的窗口类(如CWnd)与窗口过程。 
    Windows是基于事件机制的,任何窗口都能发送和处理消息,每一个窗口都对应着自己的消息处理函数,即通常所说的窗口过程(WindowProc)。窗口过程通常是在WNDCLASSEX的lpfnWndProc变量中指定的,然后调用RegisterClassEx注册窗口类,lpfnWndProc要求是全局的或是类的静态成员,而MFC的窗口和类对象是一一对应的,在类中定义的窗口过程(CWnd::WindowProc)并非类的静态成员,那么窗口消息是怎样传给窗口对象的WindowProc函数去处理的呢?MFC中定义了一个全局的AfxWndProc函数,AfxWndProc是MFC中所有的窗口共用的窗口过程。这里要注意在AfxEndDeferRegisterClass中注册窗口类时并没有把AfxWndProc赋给lpfnWndProc,而是把DefWindowProc赋给了lpfnWndProc。真正把AfxWndProc指定为窗口过程的是在CWnd::CreateEx函数中,CWnd::CreateEx中先后调用了SetWindowsHookEx、CreateWindowEx和UnhookWindowsHookEx,SetWindowsHookEx安装了一个WH_CBT类型的钩子,在调用CreateWindowEx时(在CreateWindowEx返回之前)窗口会发送WM_CREATE、 WM_NCCREATE等消息,钩子过程CBTProc会在窗口消息WM_CREATE、 WM_NCCREATE等发送前被调用,并提前得到窗口的句柄值。钩子过程CBTProc的任务是把窗口句柄赋给窗口对象(CWnd::m_hWnd),并调用SetWindowLong把窗口过程替换成AfxWndProc(如是控件还要保留原窗口过程,用CallWindowProc进行默认处理)。在这有人可能会问,为什么不在AfxEndDeferRegisterClass中直接指定AfxWndProc呢?当然是有原因的:其一是控件的窗口过程必须用SetWindowLong来替换,其二是消息WM_CREATE、 WM_NCCREATE等是在CreateWindowEx返回前发送的,CWnd::WindowProc在处理这些消息时CWnd::m_hWnd必须是已经被初始化的,这个就是由前面的CBTProc完成的。 
    好现在我们只要关注AfxWndProc了。AfxWndProc是如何把消息分配给各个窗口对象的窗口过程的呢?在MFC中有一个全局的映射表(还没到消息映射,呵呵),这个表是窗口句柄到窗口对象的映射(即通过窗口句柄就能找到窗口对象的地址),找到了窗口对象就可以把消息处理的任务交给CWnd::WindowProc了(调用pWnd- >WindowProc)。 

    下面就是消息映射了 
    其实这就简单了,因为这时只需关注CWnd::WindowProc和消处理函数(如onCreate)了。在MFC中定义了几个宏:DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP等,其实把这几个宏换回来就很好理解了。为了便于理解,我把这些宏简化一下: 
    // 
    typedef struct _MSGMAP_ENTRY 
    { 
    UINT nMessage; //消息 
    void (CWnd::*pfn)(); //消息处理函数据 
    }MSGMAP_ENTRY; 

    DECLARE_MESSAGE_MAP相当于 
    static MSGMAP_ENTRY _MessageEntry[]; //定义了一个映射表 

    BEGIN_MESSAGE_MAP、END_MESSAGE_MAP和两者之间的宏相当于 
    MSGMAP_ENTRY CWnd::_MessageEntry[] = 
    { 
    {WM_CREATE, &onCreate}, //第一个消息映射 
    {WM_CLOSE, &onClose}, //第二个消息映射 
    {0, 0} //消息映射结尾 
    }; 

    CWnd::WindowProc之不过是在_MessageEntry[]查找有没有定义的消息,如有,则调用相应的处理函数,如没有则调用CWnd::DefWindowProc 

    还想提一下Delphi中的相关处理,Delphi是不是用了同样的方法呢?答案是否定的,Delphi用汇编语句把类的非静态成员函数的地址赋给lpfnWndProc,这个也很有意思,当然用C++也可这么做。

    对于传递函做个解释如下:

    AfxWndProc()      
    该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc

    AfxCallWndProc()  
    该函数负责保存消息(保存的内容主要是消息标识符和消息参数)供应用程序以后使用,
    然后调用WindowProc()函数

    WindowProc()      
    该函数负责发送消息到OnWndMsg()函数,如果未被处理,则调用DefWindowProc()函数

    OnWndMsg()        
    该函数的功能首先按字节对消息进行排序,
    对于WM_COMMAND消息,调用OnCommand()消息响应函数,
    对于WM_NOTIFY消息调用OnNotify()消息响应函数。
    任何被遗漏的消息将是一个窗口消息。
    OnWndMsg()函数搜索类的消息映像,以找到一个能处理任何窗口消息的处理函数。
    如果OnWndMsg()函数不能找到这样的处理函数的话,则把消息返回到WindowProc()函数,
    由它将消息发送给DefWindowProc()函数

    OnCommand()       
    该函数查看这是不是一个控件通知
    (lParam参数不为NULL,如果lParam参数为空的话,说明该消息不是控件通知),
    如果它是,OnCommand()函数会试图将消息映射到制造通知的控件;
    如果他不是一个控件通知(或者如果控件拒绝映射的消息)OnCommand()就会调用OnCmdMsg()函数

    OnNotify()也试图将消息映射到制造通知的控件;
    如果映射不成功,OnNotify()就调用相同的OnCmdMsg()函数

    OnCmdMsg()        
    根据接收消息的类,
    OnCmdMsg()函数将在一个称为命令传递(Command Routing)的过程中潜在的传递命令消息和控件通知。
    例如:如果拥有该窗口的类是一个框架类,
    则命令和通知消息也被传递到视图和文档类,并为该类寻找一个消息处理函数

  • 相关阅读:
    [Java] 编写第一个java程序
    [Java] 环境变量设置
    [ActionScript 3.0] 常用的正则表达式
    [ActionScript 3.0] 正则表达式
    Python学习之==>URL编码解码&if __name__ == '__main__'
    Python学习之==>面向对象编程(一)
    Linux下安装redis-4.0.10
    Linux下编译安装Python-3.6.5
    Python学习之==>发送邮件
    Python学习之==>网络编程
  • 原文地址:https://www.cnblogs.com/lidabo/p/3447092.html
Copyright © 2011-2022 走看看