<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>
  • 網站首頁 > 物聯資訊 > 技術分享

    進入MFC講壇的前言(三)

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

    MFC中的窗口創建及窗口消息映射

    我經常碰到有人問我有關窗口創建的問題,他們經常把用HWND描述的系統窗口對象和用CWnd描述的MFC的窗口對象混淆不清。這兩者之間是緊密聯系在一起的,但是MFC為了自身的管理,在CWnd中加了一些額外的內容,包括如何從HWND生成CWnd。

      在MFC中,有幾種典型的窗口對象,CWnd描述的一般窗口對象,CView描述的視圖對象,CFrameWnd描述的SDI框窗對象,CMDIFrameWnd描述的MDI框窗對象等等。在這一章中,主要討論下述內容:

      MFC中窗口的創建

      MFC的消息映射機制(MESSAGE MAP)

      對于上面兩點MFC的設計者們使用了很高的技巧來確保應用程序的代碼盡可能小,其中的技巧和隱藏在它們背后的思想值得我們學習。下面對各項內容進行討論。

      MFC中窗口的創建

      在Window下,創建窗口可以使用兩個函數,CreateWindow()和CreateWindowEx(),它們都需要一個參數,這個參數是標識窗口類的字符串。所以,如果要創建窗口,一般的做法是,先使用RegisterClass()或RegisterClassEx()注冊一個窗口類,然后使用該窗口類來創建窗口。在前面我也提到過,注冊窗口類的最主要目的是為系統提供窗口函數的地址,以便被DispatchMessage()之類的函數回。

      在MFC中,創建窗口的函數是CWnd或其派生類的Create()或CreateEx方法,注冊窗口類一般使用AfxRegisterWndClass(),在這個全局函數中,并沒有發現窗口函數地址這樣的參數,因此腦子里自然就會有這樣的問題:窗口函數在哪里?它是如何同窗口關聯的?下面我們將對MFC的一些與此有關的代碼進行仔細分析,回答上述兩個問題。

      窗口函數

      在MFC中,有一個全局的函數AfxWndProc(),正如下面的注釋所示,它就是CWnd及所有從它派生的窗口類的窗口函數,它的實現如下:

      // The WndProc for all CWnd's and derived classes

      LRESULT CALLBACK

      AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

      {

       // special message which identifies the window as using AfxWndProc

       if (nMsg == WM_QUERYAFXWNDPROC)

       return 1;

       // all other messages route through message map

       CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

       ASSERT(pWnd != NULL);

       ASSERT(pWnd->m_hWnd == hWnd);

       return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

      }

      AfxCallWndProc()調用pWnd對象的虛擬函數WindowProc(),它的代碼如下:

      LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

      {

       // OnWndMsg does most of the work, except for DefWindowProc call

       LRESULT lResult = 0;

       if (!OnWndMsg(message, wParam, lParam, &lResult))

       lResult = DefWindowProc(message, wParam, lParam);

       return lResult;

      }

    上面的代碼中,OnWndMsg()是用來處理該窗口消息的函數,如果某條消息沒有被OnWndMsg()處理,也就是該窗口沒有提供處理該消息的函數,它就調用DefWindowProc()進行處理,DefWindowProc()也是一個虛擬函數,看看它的代碼:

      LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)

      {

       if (m_pfnSuper != NULL)

       return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

       WNDPROC pfnWndProc;

       if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)

        return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

       else

       return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);

      }

    DefWindowProc()的策略很簡單,調用基類的窗口函數m_pfnSuper來處理該消息。

      通過上面的分析,可以得出這樣的結論:與其說AfxWndProc()是MFC的唯一窗口函數,還不如說AfxWndProc()是MFC的窗口消息分發中心。正是由于有了這個消息分發中心,才使得MFC的應用程序能夠用有限的幾個窗口類,作出各種形形色色的窗口,使得在應用程序中,增加CWnd的派生類,并不增加系統中窗口類的個數,將對系統資源的使用控制在一個穩定的范圍之內。

      注冊窗口類除了提供窗口函數外,還指定該窗口的一些外觀,如是否有標題條,窗口缺省背景等等。在MFC框架中,有框窗、視圖和控制條(CControlBar)等,它們除了操作行為不同外,外觀等也不相同,所以MFC注冊了幾種缺省的窗口類。在MFC中,有一個全局函數AfxEndDeferRegisterClass(LONG fToRegister),它用來注冊MFC預定義的窗口類,包括同框窗、視圖所對應的窗口類。由于它的代碼占的篇幅很長,而且實現也很簡單,所以就不列出它的代碼了,如果你有興趣,可以在wincore.cpp中找到它的實現代碼。


      


      掛接窗口函數

      如果你考察過AfxEndDeferRegisterClass()的實現代碼,你會對一行代碼感到迷惑,下面列出的是AfxEndDeferRegisterClass()的部分代碼,帶陰影部分的是那一行令人迷惑的代碼:

      BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)

      {

       。。。。。。

       wndcls.lpfnWndProc = DefWindowProc;

       。。。。。。

      }

      MFC將所有預定義的窗口類的窗口函數都設置成DefWindowProc。大家都知道,DefWindowProc是Window下為一般窗口提供消息缺省處理的API,它肯定不是應用程序所需要的窗口函數,所以,MFC肯定在某個地方置換了它,置換DefWindowProc的代碼在哪里呢?

      前面說過,在MFC中創建窗口時是用CWnd的兩個虛擬函數Create()和CreateEx(),Create()是通過調用CreateEx()實現的,所以最終窗口的創建都要歸結到CreateEx()函數上。因此,我們可以推斷MFC在CreateEx()中置換了DefWindowProc。為了證實這一點,看看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

       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);

      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;

      }

    從上面的代碼看不出任何顯式的置換DefWindowProc的代碼,其實,它隱藏在AfxHookWindowCreate(this)之中,順藤摸瓜,再看看AfxHookWindowCreate()的代碼:

      void AFXAPI AfxHookWindowCreate(CWnd* pWnd)

      {

       _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();

       if (pThreadState->m_pWndInit == pWnd)

       return;

       if (pThreadState->m_hHookOldCbtFilter == NULL)

       {

        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

       pThreadState->m_pWndInit = pWnd;

      }

    AfxHookWindowCreate()設置了一個線程級的CBT Hook,該Hook的入口地址為_AfxCbtFilterHook,_AfxCbtFilterHook是一個全局的MFC函數,_AfxCbtFilterHook通過調用SetWindowLong()用AfxWndProc()的入口地址置換掉DefWindowProc。

      小結

      1、在MFC中,創建一個窗口的過程是:

        1、生成一個對應窗口類的對象

        2、調用該對象的Create()或CreateEx()方法

        3、在CreateEx()中(Create()是調用CreateEx()實現的),先調用虛擬函數PreCreateWindow(),讓應用程序有一個改變窗口行為的機會,同時,在PreCreateWindow中,還作了一件很重要的事情,就是如果指向窗口類的字符串指針為NULL,就調用AfxDeferRegisterClass()注冊一個合適的窗口類,將該注冊的窗口類作為創建窗口的類型參數。AfxDeferRegisterClass()就是AfxEndDeferRegisterClass(),后者根據參數注冊相應的窗口類,并將DefWindowProc作為該窗口類的臨時窗口函數。

        4、CreateEx()在調用CreateWindowEx()創建真正的窗口對象之前,設置一個線程級的CBT Hook,該hook在窗口創建完成后被調用,MFC在hook函數中調用SetWindowLong()將該窗口的窗口函數置換成AfxWndProc。

      2、從Window的角度看,任何一個MFC應用程序都只有一個窗口函數AfxWndProc。

      3、AfxWndProc的作用是截獲所有的發送給窗口消息,并將這些消息發送給相應的窗口對象的窗口函數WindowProc處理,所以它實質上是一個窗口消息分發器,注意,非窗口消息不被AfxWndProc所分發,它們在AfxWndProc被調用之前就被CWinThread::PreTranslateMessage()處理過了。

      4、同PreTranslateMessage()不同的地方在于,AfxWndProc()能夠截獲所有的來自于消息隊列的和非消息隊列的窗口消息(如調用SendMessage()發送的窗口消息)。
    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全