<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    vc++窗口的創建過程(MFC消息機制的經典文章)

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    一、什么是窗口類 
      在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)的過程中潛在的傳遞命令消息和控件通知。
    例如:如果擁有該窗口的類是一個框架類,
    則命令和通知消息也被傳遞到視圖和文檔類,并為該類尋找一個消息處理函數

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全