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

    (1)每個(gè)應(yīng)用程序都有且僅有一個(gè)應(yīng)用類的全局變量theApp,全局變量先于WinMain函數(shù)進(jìn)行處理。
    (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();
    其實(shí)這里得到的這兩個(gè)指針都是指向全局的對象theApp的;
    接下來有函數(shù)調(diào)用pThread->InitInstance(),根據(jù)多態(tài)性,會調(diào)用CXXXApp類中的InitInstance()函數(shù)。該函數(shù)很重要,在對該函數(shù)的調(diào)用中就會完成:設(shè)計(jì)窗口類->注冊窗口類->生成窗口->顯示窗口->更新窗口。
    接下來,該函數(shù)中會繼續(xù)調(diào)用pThread->Run(),這就完成了:消息循環(huán)->消息路由到窗口過程函數(shù)處理。
    (4)進(jìn)入CXXXApp::InitInstance()函數(shù)體中,對于單文檔應(yīng)用程序,調(diào)用ProcessShellCommand(cmdInfo),通過調(diào)用該函數(shù)就會完成:設(shè)計(jì)窗口類->注冊窗口類->生成窗口。
    再接下來就會調(diào)用m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();這就完成了:顯示窗口->更新窗口。
    (5)在函數(shù)CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)中會進(jìn)入到如下的case分支:case CCommandLineInfo::FileNew:
      if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
    (6)進(jìn)入函數(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)進(jìn)入函數(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)進(jìn)入CWinApp::OnFileNew(),調(diào)用m_pDocManager->OnFileNew();這個(gè)函數(shù)很特殊,它本身是個(gè)消息響應(yīng)函數(shù),當(dāng)我們點(diǎn)擊ID為ID_FILE_NEW的菜單時(shí),會產(chǎn)生一個(gè)命令消息,由于命令消息可以被CCmdTarget類及其派生類來捕獲,而CWinApp是從CCmdTarget派生出來的,因此可以捕獲這個(gè)消息。當(dāng)應(yīng)用程序創(chuàng)建完成并成功顯示后,當(dāng)我們點(diǎn)擊文件菜單下的新建菜單項(xiàng)時(shí),就會首先進(jìn)入這個(gè)函數(shù),然后再依次執(zhí)行下去,最后就會執(zhí)行到pDocument->OnNewDocument()中,往往我們會對這個(gè)函數(shù)不解,不知道它為什么會響應(yīng)ID_FILE_NEW的命令消息,至此真相大白了。順便說一句,為什么程序在剛啟動的時(shí)候,我們并沒有點(diǎn)擊菜單項(xiàng),為什么會自動的產(chǎn)生這個(gè)消息呢?這是因?yàn)樵贑XXXXApp::InitInstance()函數(shù)中有“CCommandLineInfo cmdInfo;”這個(gè)類的構(gòu)造函數(shù)是這樣的:CCommandLineInfo::CCommandLineInfo()
    {
     m_bShowSplash = TRUE;
     m_bRunEmbedded = FALSE;
     m_bRunAutomated = FALSE;
     m_nShellCommand = FileNew;
    },因此就會在第(5)步驟的時(shí)候進(jìn)入到“case CCommandLineInfo::FileNew:”這個(gè)分支中,就相當(dāng)于產(chǎn)生了這樣一個(gè)FileNew的消息。同理對于ID為ID_FILE_OPEN(在CWinApp::OnFileOpen()中響應(yīng))、ID_FILE_SAVE(在CDocument::OnFileSave()中響應(yīng))等等在MFC向?qū)槲覀兩傻膯挝臋n類中找不到消息響應(yīng)的入口時(shí),其實(shí)都是在基類CWinApp或者CDocument類中進(jìn)行了響應(yīng)。對于CXXXXDoc::Serialize(CArchive& ar)函數(shù)也是通過ID_FILE_SAVE和ID_FILE_OPEN產(chǎn)生命令消息后就行響應(yīng)從而才調(diào)用該函數(shù)的。
    (9)進(jìn)入CDocManager::OnFileNew(),CDocManager類有一個(gè)成員變量是CPtrList m_templateList;該變量保存了一個(gè)文檔模版鏈表指針,在CDocManager::OnFileNew()函數(shù)體中會調(diào)用CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();得到鏈表中的頭,也就是第一個(gè)文檔模版,后面就會用得到的這個(gè)指針去調(diào)用pTemplate->OpenDocumentFile(NULL);緊接著就會有一個(gè)判斷,用來確定該鏈表中是否只有一項(xiàng),如果鏈表中保存了多個(gè)文檔模版,則會彈出一個(gè)對話框,來讓我們選擇到底是使用哪一套文檔模版來構(gòu)建應(yīng)用程序,相信大家也都見到過這種情況吧。對了,還有一點(diǎn)要說明的是:pTemplate是一個(gè)CDocTemplate的指針,但接下來程序?yàn)槭裁磿M(jìn)入到CSingleDocTemplate::OpenDocumentFile的函數(shù)體內(nèi)呢,這是因?yàn)镃DocTemplate類中的OpenDocumentFile函數(shù)被定義為純虛函數(shù),而CSingleDocTemplate類又是從CDocTemplate類派生出來的,并且實(shí)現(xiàn)了該函數(shù),因此就會進(jìn)入到子類的函數(shù)體中了。
    (10)進(jìn)入CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
     BOOL bMakeVisible),先調(diào)用CreateNewDocument()創(chuàng)建文檔類,再調(diào)用pFrame = CreateNewFrame(pDocument, NULL);創(chuàng)建框架類和視圖類,從這里也可以看出MFC體系結(jié)構(gòu)中文檔、框架、視圖“三位一體”的模式,在這一個(gè)函數(shù)中同時(shí)創(chuàng)建三個(gè)類;再會調(diào)用pDocument->OnNewDocument();因此就會進(jìn)入到子類的文檔類中的pDocument->OnNewDocument()中了。
    (11)進(jìn)入CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther),調(diào)用if (!pFrame->LoadFrame(m_nIDResource,
       WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles
       NULL, &context))
    (12)進(jìn)入BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
     CWnd* pParentWnd, CCreateContext* pContext),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
    (13)進(jìn)入BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister),該函數(shù)內(nèi)部就完成了:設(shè)計(jì)窗口類->注冊窗口類。MFC通過給我們提供好一些已經(jīng)訂制好的窗口類,我們不需要自己再設(shè)計(jì)窗口類,只需要到那些訂制好的窗口類“倉庫”中尋找一種適合我們需要的窗口類就可以了,然后通過AfxRegisterClass函數(shù)注冊窗口類。還需要說明的是,再后續(xù)的跟蹤過程中,我們會發(fā)現(xiàn)還會進(jìn)入到AfxEndDeferRegisterClass函數(shù)中進(jìn)行設(shè)計(jì)和注冊窗口類,這主要是因?yàn)閱挝臋n應(yīng)用程序比較特殊,它提前通過這樣的一種途徑進(jìn)行了窗口類的設(shè)計(jì)和注冊步驟,其實(shí)是應(yīng)該在BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)函數(shù)的調(diào)用中完成窗口類的設(shè)計(jì)和注冊的,這一點(diǎn)我們要清楚,也就是說設(shè)計(jì)和注冊窗口類的正宗發(fā)源地應(yīng)該是PreCreateWindow(CREATESTRUCT& cs)。此外,我們還會注意到在該函數(shù)體的前部分有一語句為“wndcls.lpfnWndProc = DefWindowProc;”因此所有窗口類的窗口過程函數(shù)都是DefWindowProc,這一點(diǎn)在后面的跟蹤中可以看到,每次生成窗口之后都會調(diào)用幾次DefWindowProc函數(shù)。也就是說MFC都是讓我們采用默認(rèn)的窗口過程函數(shù),這并不是說我們因此就不能使用自己的窗口過程函數(shù)實(shí)現(xiàn)個(gè)性化的消息處理了,MFC采用了一種基于消息映射的機(jī)制完成了消息個(gè)性化處理。
    (14)回到BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
     CWnd* pParentWnd, CCreateContext* pContext)中,調(diào)用LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
    (15)進(jìn)入LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource),調(diào)用PreCreateWindow(cs);
    (16)進(jìn)入BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs),調(diào)用CFrameWnd::PreCreateWindow(cs)
    (17)進(jìn)入BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs),調(diào)用VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));又一次設(shè)計(jì)和注冊窗口類
    (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)進(jìn)入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)進(jìn)入int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct),調(diào)用if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
    (22)進(jìn)入int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs),調(diào)用return OnCreateHelper(lpcs, pContext);
    (23)進(jìn)入int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext),調(diào)用if (CWnd::OnCreate(lpcs) == -1)
    (24)進(jìn)入_AFXWIN_INLINE int CWnd::OnCreate(LPCREATESTRUCT),調(diào)用return (int)Default();
    (25)進(jìn)入LRESULT CWnd::Default(),調(diào)用return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam);
    (26)進(jìn)入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)進(jìn)入BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext),調(diào)用if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
    (29)進(jìn)入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)進(jìn)入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)進(jìn)入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),重復(fù)生成框架類CMainFrame的過程來生成CXXXView,因?yàn)樗彩且粋€(gè)窗口類,因此也需要進(jìn)行那一系列過程才能最終顯示更新出來。
    調(diào)用的順序是這個(gè)樣子的: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)的,因?yàn)樵谏纱翱诘臅r(shí)候需要響應(yīng)一些消息,因此需要調(diào)用一些窗口過程函數(shù),每次在調(diào)用::CreateWindowEx(...)函數(shù)后都會調(diào)用一些窗口過程函數(shù),然后再去調(diào)用該窗口類對應(yīng)的OnCreate函數(shù),其實(shí)在調(diào)用OnCreate函數(shù)之前調(diào)用CreateWindowEx只是生成了一個(gè)窗口,至于這個(gè)窗口里面要放置些什么東西,以及該如何裝飾該窗口,則就需要由OnCreate來完成了,往往我們都會在OnCreate函數(shù)的后面(這樣做是為了不影響窗口本身應(yīng)該布置的格局)添加一些代碼,創(chuàng)建我們自己的東西,比如我們通常會在CMainFrame類的OnCreate函數(shù)后面放置一些Create代碼,來創(chuàng)建我們自己的可停靠的工具欄或者按鈕之類的東西,當(dāng)然我們也可以在CXXXView類的OnCreate函數(shù)的后面添加一些代碼,來創(chuàng)建我們需要的東西,比如按鈕之類的東西。在完成了從設(shè)計(jì)、注冊到生成窗口的過程之后,往往還需要顯示更新,有些時(shí)候,我們不必要每次都顯示的調(diào)用CWnd的ShowWindow和UpdateWindow兩個(gè)函數(shù),我們可以在創(chuàng)建的時(shí)候,給窗口風(fēng)格中添加WS_VISIBLE即可,因此有些時(shí)候會跟蹤不到ShowWindow和UpdateWindow兩個(gè)函數(shù)這兩個(gè)函數(shù),因?yàn)榇翱谠趧?chuàng)建的時(shí)候就可見了。
        總的來說,先初始化應(yīng)用類,然后注冊生成框架類,然后再注冊生成視圖類,然后注冊生成視圖類OnCreate函數(shù)后面用戶添加的、用Create來準(zhǔn)備創(chuàng)建的窗口,然后再注冊生成框架類的OnCreate函數(shù)后面需要生成的m_wndToolBar、m_wndStatusBar以及我們自己添加的要?jiǎng)?chuàng)建的窗口類,最后在回到應(yīng)用類的初始化的函數(shù)體中,調(diào)用框架類的顯示和更新函數(shù),然后再進(jìn)入由框架類定義的窗口的消息循環(huán)中。

    消息循環(huán)的過程是這個(gè)樣子的:
    (1)調(diào)用int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
     LPTSTR lpCmdLine, int nCmdShow)函數(shù)中的pThread->Run()
    (2)進(jìn)入int CWinApp::Run(),調(diào)用return CWinThread::Run();
    (3)進(jìn)入int CWinThread::Run(),調(diào)用if (!PumpMessage())
    (4)進(jìn)入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)再重復(fù)(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));
    這段代碼其實(shí)本質(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程序框架剖析  回復(fù)  更多評論   

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

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

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

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

    多謝樓主,獲益良多!
    2009-12-05 20:30 | mfc
    主站蜘蛛池模板: 亚洲人成电影网站色| 亚洲国产成人AV在线播放| 亚洲精品无码一区二区| 精品一区二区三区免费毛片爱 | 免费看一级一级人妻片| 免费下载成人电影| 亚洲视频国产精品| 中国一级全黄的免费观看| 日本午夜免费福利视频| 7777久久亚洲中文字幕| 四虎在线最新永久免费| 内射少妇36P亚洲区| 免费不卡在线观看AV| 久久亚洲精品人成综合网| 久草福利资源网站免费| 亚洲永久中文字幕在线| 国产a视频精品免费观看| 亚洲av午夜成人片精品电影| 高潮毛片无遮挡高清免费| 99在线观看免费视频| 久久久久亚洲AV成人片| 18禁无遮挡无码国产免费网站| 亚洲高清美女一区二区三区| 13一14周岁毛片免费| 亚洲а∨天堂久久精品9966 | 日韩亚洲国产综合高清| 狼友av永久网站免费观看| 日本高清不卡中文字幕免费| 国产亚洲色婷婷久久99精品91| 免费观看在线禁片| 亚洲中文字幕无码久久2020| 又黄又爽一线毛片免费观看| 久久精品成人免费国产片小草| 久久久亚洲欧洲日产国码二区| 成年女人毛片免费播放视频m| 深夜A级毛片视频免费| 亚洲不卡在线观看| 国产AⅤ无码专区亚洲AV| 日韩吃奶摸下AA片免费观看| www一区二区www免费| 中文字幕乱码亚洲精品一区|