<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    so true

    心懷未來,開創(chuàng)未來!
    隨筆 - 160, 文章 - 0, 評論 - 40, 引用 - 0
    數(shù)據(jù)加載中……

    MFC程序框架剖析

    即便是基于MFC的應用程序,建立窗口類也是會遵循如下的過程:
    設(shè)計窗口類->注冊窗口類->生成窗口->顯示窗口->更新窗口->消息循環(huán)->消息路由到窗口過程函數(shù)處理。下面就剖析一下在MFC中是如何完成上述過程的。

    (1)每個應用程序都有且僅有一個應用類的全局變量theApp,全局變量先于WinMain函數(shù)進行處理。
    (2)WinMain函數(shù)體在APPMODUL.CPP文件中,定義如下:
    extern "C" int WINAPI
    _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
     LPTSTR lpCmdLine, int nCmdShow)
    {
     // call shared/exported WinMain
     return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
    }
    其中#define _tWinMain   WinMain
    (3)AfxWinMain函數(shù)體在WINMAIN.CPP文件中,里面有如下兩句話:
    CWinThread* pThread = AfxGetThread();
    CWinApp* pApp = AfxGetApp();
    其實這里得到的這兩個指針都是指向全局的對象theApp的;
    接下來有函數(shù)調(diào)用pThread->InitInstance(),根據(jù)多態(tài)性,會調(diào)用CXXXApp類中的InitInstance()函數(shù)。該函數(shù)很重要,在對該函數(shù)的調(diào)用中就會完成:設(shè)計窗口類->注冊窗口類->生成窗口->顯示窗口->更新窗口。
    接下來,該函數(shù)中會繼續(xù)調(diào)用pThread->Run(),這就完成了:消息循環(huán)->消息路由到窗口過程函數(shù)處理。
    (4)進入CXXXApp::InitInstance()函數(shù)體中,對于單文檔應用程序,調(diào)用ProcessShellCommand(cmdInfo),通過調(diào)用該函數(shù)就會完成:設(shè)計窗口類->注冊窗口類->生成窗口。
    再接下來就會調(diào)用m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();這就完成了:顯示窗口->更新窗口。
    (5)在函數(shù)CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)中會進入到如下的case分支:case CCommandLineInfo::FileNew:
      if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
    (6)進入函數(shù)CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
     AFX_CMDHANDLERINFO* pHandlerInfo),調(diào)用_AfxDispatchCmdMsg(this, nID, nCode,
        lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
    (7)進入函數(shù)AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
     AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo),調(diào)用
    case AfxSig_vv:
      // normal command or control notification
      ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
      ASSERT(pExtra == NULL);
      (pTarget->*mmf.pfn_COMMAND)();
    (8)進入CWinApp::OnFileNew(),調(diào)用m_pDocManager->OnFileNew();這個函數(shù)很特殊,它本身是個消息響應函數(shù),當我們點擊ID為ID_FILE_NEW的菜單時,會產(chǎn)生一個命令消息,由于命令消息可以被CCmdTarget類及其派生類來捕獲,而CWinApp是從CCmdTarget派生出來的,因此可以捕獲這個消息。當應用程序創(chuàng)建完成并成功顯示后,當我們點擊文件菜單下的新建菜單項時,就會首先進入這個函數(shù),然后再依次執(zhí)行下去,最后就會執(zhí)行到pDocument->OnNewDocument()中,往往我們會對這個函數(shù)不解,不知道它為什么會響應ID_FILE_NEW的命令消息,至此真相大白了。順便說一句,為什么程序在剛啟動的時候,我們并沒有點擊菜單項,為什么會自動的產(chǎn)生這個消息呢?這是因為在CXXXXApp::InitInstance()函數(shù)中有“CCommandLineInfo cmdInfo;”這個類的構(gòu)造函數(shù)是這樣的:CCommandLineInfo::CCommandLineInfo()
    {
     m_bShowSplash = TRUE;
     m_bRunEmbedded = FALSE;
     m_bRunAutomated = FALSE;
     m_nShellCommand = FileNew;
    },因此就會在第(5)步驟的時候進入到“case CCommandLineInfo::FileNew:”這個分支中,就相當于產(chǎn)生了這樣一個FileNew的消息。同理對于ID為ID_FILE_OPEN(在CWinApp::OnFileOpen()中響應)、ID_FILE_SAVE(在CDocument::OnFileSave()中響應)等等在MFC向?qū)槲覀兩傻膯挝臋n類中找不到消息響應的入口時,其實都是在基類CWinApp或者CDocument類中進行了響應。對于CXXXXDoc::Serialize(CArchive& ar)函數(shù)也是通過ID_FILE_SAVE和ID_FILE_OPEN產(chǎn)生命令消息后就行響應從而才調(diào)用該函數(shù)的。
    (9)進入CDocManager::OnFileNew(),CDocManager類有一個成員變量是CPtrList m_templateList;該變量保存了一個文檔模版鏈表指針,在CDocManager::OnFileNew()函數(shù)體中會調(diào)用CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();得到鏈表中的頭,也就是第一個文檔模版,后面就會用得到的這個指針去調(diào)用pTemplate->OpenDocumentFile(NULL);緊接著就會有一個判斷,用來確定該鏈表中是否只有一項,如果鏈表中保存了多個文檔模版,則會彈出一個對話框,來讓我們選擇到底是使用哪一套文檔模版來構(gòu)建應用程序,相信大家也都見到過這種情況吧。對了,還有一點要說明的是:pTemplate是一個CDocTemplate的指針,但接下來程序為什么會進入到CSingleDocTemplate::OpenDocumentFile的函數(shù)體內(nèi)呢,這是因為CDocTemplate類中的OpenDocumentFile函數(shù)被定義為純虛函數(shù),而CSingleDocTemplate類又是從CDocTemplate類派生出來的,并且實現(xiàn)了該函數(shù),因此就會進入到子類的函數(shù)體中了。
    (10)進入CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
     BOOL bMakeVisible),先調(diào)用CreateNewDocument()創(chuàng)建文檔類,再調(diào)用pFrame = CreateNewFrame(pDocument, NULL);創(chuàng)建框架類和視圖類,從這里也可以看出MFC體系結(jié)構(gòu)中文檔、框架、視圖“三位一體”的模式,在這一個函數(shù)中同時創(chuàng)建三個類;再會調(diào)用pDocument->OnNewDocument();因此就會進入到子類的文檔類中的pDocument->OnNewDocument()中了。
    (11)進入CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther),調(diào)用if (!pFrame->LoadFrame(m_nIDResource,
       WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles
       NULL, &context))
    (12)進入BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
     CWnd* pParentWnd, CCreateContext* pContext),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
    (13)進入BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister),該函數(shù)內(nèi)部就完成了:設(shè)計窗口類->注冊窗口類。MFC通過給我們提供好一些已經(jīng)訂制好的窗口類,我們不需要自己再設(shè)計窗口類,只需要到那些訂制好的窗口類“倉庫”中尋找一種適合我們需要的窗口類就可以了,然后通過AfxRegisterClass函數(shù)注冊窗口類。還需要說明的是,再后續(xù)的跟蹤過程中,我們會發(fā)現(xiàn)還會進入到AfxEndDeferRegisterClass函數(shù)中進行設(shè)計和注冊窗口類,這主要是因為單文檔應用程序比較特殊,它提前通過這樣的一種途徑進行了窗口類的設(shè)計和注冊步驟,其實是應該在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)函數(shù)的調(diào)用中完成窗口類的設(shè)計和注冊的,這一點我們要清楚,也就是說設(shè)計和注冊窗口類的正宗發(fā)源地應該是PreCreateWindow(CREATESTRUCT& cs)。此外,我們還會注意到在該函數(shù)體的前部分有一語句為“wndcls.lpfnWndProc = DefWindowProc;”因此所有窗口類的窗口過程函數(shù)都是DefWindowProc,這一點在后面的跟蹤中可以看到,每次生成窗口之后都會調(diào)用幾次DefWindowProc函數(shù)。也就是說MFC都是讓我們采用默認的窗口過程函數(shù),這并不是說我們因此就不能使用自己的窗口過程函數(shù)實現(xiàn)個性化的消息處理了,MFC采用了一種基于消息映射的機制完成了消息個性化處理。
    (14)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
     CWnd* pParentWnd, CCreateContext* pContext)中,調(diào)用LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
    (15)進入LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource),調(diào)用PreCreateWindow(cs);
    (16)進入BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs),調(diào)用CFrameWnd::PreCreateWindow(cs)
    (17)進入BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));又一次設(shè)計和注冊窗口類
    (18)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
     CWnd* pParentWnd, CCreateContext* pContext)中,調(diào)用if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
    (19)進入BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
     LPCTSTR lpszWindowName,
     DWORD dwStyle,
     const RECT& rect,
     CWnd* pParentWnd,
     LPCTSTR lpszMenuName,
     DWORD dwExStyle,
     CCreateContext* pContext),調(diào)用if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
      rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
      pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
    (20)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),調(diào)用if (!PreCreateWindow(cs))
    ,接下來調(diào)用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);好了,終于讓我們找到生成窗口的地方了——函數(shù)::CreateWindowEx!
    (21)進入int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct),調(diào)用if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
    (22)進入int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs),調(diào)用return OnCreateHelper(lpcs, pContext);
    (23)進入int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),調(diào)用if (CWnd::OnCreate(lpcs) == -1)
    (24)進入_AFXWIN_INLINE int CWnd::OnCreate(LPCREATESTRUCT),調(diào)用return (int)Default();
    (25)進入LRESULT CWnd::Default(),調(diào)用return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam);
    (26)進入LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam),調(diào)用return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
    (27)回到int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),調(diào)用if (!OnCreateClient(lpcs, pContext))
    (28)進入BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext),調(diào)用if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
    (29)進入CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID),調(diào)用if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nID, pContext))
    (30)進入BOOL CWnd::Create(LPCTSTR lpszClassName,
     LPCTSTR lpszWindowName, DWORD dwStyle,
     const RECT& rect,
     CWnd* pParentWnd, UINT nID,
     CCreateContext* pContext),調(diào)用return CreateEx(0, lpszClassName, lpszWindowName,
      dwStyle | WS_CHILD,
      rect.left, rect.top,
      rect.right - rect.left, rect.bottom - rect.top,
      pParentWnd->GetSafeHwnd(), (HMENU)nID, (LPVOID)pContext);
    (31)進入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),重復生成框架類CMainFrame的過程來生成CXXXView,因為它也是一個窗口類,因此也需要進行那一系列過程才能最終顯示更新出來。
    調(diào)用的順序是這個樣子的:PreCreateWindow(cs)->BOOL CXXXView::PreCreateWindow(CREATESTRUCT& cs)->CView::PreCreateWindow(cs)->VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));->::CreateWindowEx(...)->CWnd::DefWindowProc->::CallWindowProc(...)->...->CXXXView::OnCreate->CView::OnCreate->CWnd::OnCreate->...
    寫到這里,基本上就清楚了,中間的省略號表示的部分大多數(shù)都是在與窗口過程函數(shù)有關(guān)的,因為在生成窗口的時候需要響應一些消息,因此需要調(diào)用一些窗口過程函數(shù),每次在調(diào)用::CreateWindowEx(...)函數(shù)后都會調(diào)用一些窗口過程函數(shù),然后再去調(diào)用該窗口類對應的OnCreate函數(shù),其實在調(diào)用OnCreate函數(shù)之前調(diào)用CreateWindowEx只是生成了一個窗口,至于這個窗口里面要放置些什么東西,以及該如何裝飾該窗口,則就需要由OnCreate來完成了,往往我們都會在OnCreate函數(shù)的后面(這樣做是為了不影響窗口本身應該布置的格局)添加一些代碼,創(chuàng)建我們自己的東西,比如我們通常會在CMainFrame類的OnCreate函數(shù)后面放置一些Create代碼,來創(chuàng)建我們自己的可停靠的工具欄或者按鈕之類的東西,當然我們也可以在CXXXView類的OnCreate函數(shù)的后面添加一些代碼,來創(chuàng)建我們需要的東西,比如按鈕之類的東西。在完成了從設(shè)計、注冊到生成窗口的過程之后,往往還需要顯示更新,有些時候,我們不必要每次都顯示的調(diào)用CWnd的ShowWindow和UpdateWindow兩個函數(shù),我們可以在創(chuàng)建的時候,給窗口風格中添加WS_VISIBLE即可,因此有些時候會跟蹤不到ShowWindow和UpdateWindow兩個函數(shù)這兩個函數(shù),因為窗口在創(chuàng)建的時候就可見了。
        總的來說,先初始化應用類,然后注冊生成框架類,然后再注冊生成視圖類,然后注冊生成視圖類OnCreate函數(shù)后面用戶添加的、用Create來準備創(chuàng)建的窗口,然后再注冊生成框架類的OnCreate函數(shù)后面需要生成的m_wndToolBar、m_wndStatusBar以及我們自己添加的要創(chuàng)建的窗口類,最后在回到應用類的初始化的函數(shù)體中,調(diào)用框架類的顯示和更新函數(shù),然后再進入由框架類定義的窗口的消息循環(huán)中。

    消息循環(huán)的過程是這個樣子的:
    (1)調(diào)用int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
     LPTSTR lpCmdLine, int nCmdShow)函數(shù)中的pThread->Run()
    (2)進入int CWinApp::Run(),調(diào)用return CWinThread::Run();
    (3)進入int CWinThread::Run(),調(diào)用if (!PumpMessage())
    (4)進入BOOL CWinThread::PumpMessage(),調(diào)用if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
    (5)回到BOOL CWinThread::PumpMessage(),調(diào)用::TranslateMessage(&m_msgCur);::DispatchMessage(&m_msgCur);
    (6)回到int CWinThread::Run(),調(diào)用while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
    (7)再重復(4)-(6)的步驟
    下面給出int CWinThread::Run()中消息循環(huán)的部分代碼:
    do
      {
       // pump message, but quit on WM_QUIT
       if (!PumpMessage())
        return ExitInstance();

       // reset "no idle" state after pumping "normal" message
       if (IsIdleMessage(&m_msgCur))
       {
        bIdle = TRUE;
        lIdleCount = 0;
       }

      } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
    這段代碼其實本質(zhì)上與我們基于Win32 SDK手寫的代碼:
    //消息循環(huán)
     MSG msg;
     while(GetMessage(&msg,NULL,0,0))
     {
      //簡單的說,函數(shù)TranslateMessage就是把WM_KEYDOWN和WM_KEYUP翻譯成WM_CHAR消息,沒有該函數(shù)就不能產(chǎn)生WM_CHAR消息。
      TranslateMessage(&msg);
      ::DispatchMessage(&msg);
     }
    是一致的。

    posted on 2008-02-23 19:15 so true 閱讀(2503) 評論(3)  編輯  收藏 所屬分類: C&C++

    評論

    # re: MFC程序框架剖析  回復  更多評論   

    非常感謝樓主
    2008-03-11 09:12 |

    # re: MFC程序框架剖析  回復  更多評論   

    非常感謝,能提供這么好的講解,再次說聲謝謝
    2008-03-27 16:59 | BOYS

    # re: MFC程序框架剖析  回復  更多評論   

    多謝樓主,獲益良多!
    2009-12-05 20:30 | mfc
    主站蜘蛛池模板: 亚洲日本在线免费观看| 亚洲第一视频网站| 亚洲乱码av中文一区二区| 6080午夜一级毛片免费看 | 国产jizzjizz免费看jizz| 亚洲人成自拍网站在线观看| 女人与禽交视频免费看| 亚洲精品理论电影在线观看| 日韩一区二区免费视频| 羞羞漫画页面免费入口欢迎你| 亚洲AV永久无码精品一区二区国产 | 亚洲伊人久久精品影院| 日韩精品免费视频| 亚洲高清视频在线播放| 97无码免费人妻超级碰碰碰碰| 亚洲色大成网站www永久男同| 日韩中文无码有码免费视频 | 无码一区二区三区免费| 亚洲天堂电影在线观看| 免费高清小黄站在线观看| 青青免费在线视频| 亚洲av无码一区二区三区乱子伦 | 在线a人片天堂免费观看高清| 激情婷婷成人亚洲综合| 国产亚洲av片在线观看18女人| 国产成人免费ā片在线观看老同学 | 妻子5免费完整高清电视| 亚洲精品无码日韩国产不卡av| 亚洲成A∨人片天堂网无码| 最近免费mv在线观看动漫| 亚洲精品视频在线免费| 免费看又爽又黄禁片视频1000| 草久免费在线观看网站| 久久亚洲国产成人精品性色 | 亚洲色爱图小说专区| 1000部拍拍拍18勿入免费视频软件 | 亚洲今日精彩视频| 日本不卡视频免费| 久久免费高清视频| 亚洲AV无码专区在线电影成人 | 亚洲丰满熟女一区二区v|