MFC函數啓動執行過程

瞭解MFC程序的啓動過程,對於初學者來講,了學習MFC很有幫助;對於不常用VC的人來說,過一段時間就會忘記。還是來記下來,方便以後查閱。

1、創建Application object對象theApp

     程序一開始生產一個(且只有一個)Application object對象theApp,也即一個CWinApp對象,這個全局對象一產生,便執行其構造函數,因爲並沒有定義CMyWinApp構造函數,所以即執行CWinApp類的構造函數。該函數定義於APPCORE.CPP第75行,你可以自己搜出來啃一啃,因此,CWinApp之中的成員變量將因爲theApp這個全局對象的誕生而獲得配置與初值。

2、WinMain登場

     用SDK編程序時,程序的入口點是WinMain函數,而在MFC程序裏我們並沒有看到WinMain函數,哦!~ 原來她是被隱藏在MFC代碼裏面了。當theApp配置完成後,WinMain登場,慢!細看程序,並沒連到WinMain函數的代碼啊!這個我也不知道,MFC早已準備好並由鏈接器直接加到應用程序代碼中了,原來她在APPMODUL.CPP裏面,好,我們就認爲當theApp配置完成後,程序就轉到APPMODUL.CPP來了。那執行什麼呢?看看下面從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);
     }

     _tWinMain函數的“_t”是爲了支持Unicode而準備的一個宏。

     _tWinMain函數返回值是AfxWinMain函數的返回值,AfxWinMain函數定義於WINMAIN.CPP第21行,稍加整理,去蕪存菁,就可以看到這個“程序進入點”主要做些什麼事:

     int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
     {
         int nReturnCode = -1;
         CWinApp* pApp = AfxGetApp();

         AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);

         pApp->InitApplication();
         pApp->InitInstance()
         nReturnCode = pApp->Run();

         AfxWinTerm();
         return nReturnCode;
     }

     AfxGetApp()函數是取得CMyWinApp對象指針,故上面函數第6至8行相當於調用:

     CMyWinApp::InitApplication();
     CMyWinApp::InitInstance()
     CMyWinApp::Run();

     因而導致調用:
     CWinApp::InitApplication();   //因爲 CMyWinApp 並沒有改寫 InitApplication
     CMyWinApp::InitInstance()     //因爲 CMyWinApp 改寫了 InitInstance
     CWinApp::Run();               //因爲 CMyWinApp 並沒有改寫 Run

     用過SDK寫程序的朋友,現在可能會發出會心的微笑。

3、AfxWinInit——AFX內部初始化操作

     AfxWinInit是繼CWinApp構造函數之後的第一個操作,主要做的是AFX內部初始化操作,該函數定義於APPINIT.CPP第24行,這裏就不掏出來了,你自己搜出來啃吧!

4、執行CWinApp::InitApplication

     AfxWinInit之後的操作是pApp->InitApplication,我們已知道pApp指向CMyWinApp對象,當調用:

         pApp->InitApplication();

     相當於調用:

         CMyWinApp::InitApplication();

     但是你要知道,CMyWinApp繼承自CWinApp,而InitApplication又是CWinApp的一個虛擬函數,我們並沒有改寫它(大部分情況下不需改寫它),所以上述操作相當於調用:

         CWinApp::InitApplication();

     此函數定義於APPCORE.CPP第125行,你自己搜出來看吧!我就不搬出來了,裏面的操作都是MFC爲了內部管理而做的(其實我也看不懂,知道有這回事就好了)。

5、執行CWinApp::InitInstance

     繼InitApplication函數之後,AfxWinMain調用pApp->InitInstance。當程序調用:

         pApp->InitInstance();

     相當於調用:

         CMyWinApp::InitInstance();

     但是你要知道,CMyWinApp繼承自CWinApp,而InitInstance又是CWinApp的一個虛擬函數。由於我們改寫了它,所以上述操作就是調用我們自己(CMyWinApp)的這個InitInstance函數。

6、CFrameWnd::Create產生主窗口(並先註冊窗口類)

     現在已經來到CWinApp::InitInstance了,該函數先new一個CMyFrameWnd對象,從而產生主窗口。在創建CMyFrameWnd對之前,要先執行構造函數CMyFrameWnd::CMyFrameWnd(),該函數用Create函數產生窗口:

         CMyFrameWnd::CMyFrameWnd()
         {
             Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, "MainMenu");
         }

     其中Create是CFrameWnd的成員函數,它將產生一個窗口,用過SDK編程序的朋友都知道,要創建主窗口時要先註冊一個窗口類,規定窗口的屬性等,但,這裏使用哪一個窗口類呢?Create函數第一個參數(其它參數請參考MSDN或《深出淺出MFC》詳解)指定窗口類設爲NULL又是什麼意思啊?意思是要以MFC內建的空中類產生一個標準的外框窗口,但,我們的程序一般都沒有註冊任何窗口類呀!噢,Create函數在產生窗口之前會引發窗口類的註冊操作。

     讓我們先挖出Create函數都做了些什麼操作,Create函數定義於WINFRM.CPP的第538行(在此我就不把代碼Copy過來了,你自己打開出來看吧),函數在562行調用CreateEx函數,由於CreateEx是CWnd的成員函數,而CFrameWnd是從CWnd繼而來,故將調用CWnd::CreateEx。此函數定義於WINCORE.CPP第665行,下面是部分代碼:

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

     用過SDK編程序的朋友,看到上面代碼應該有一點感覺了吧,函數中調用的PreCreateWindows是虛擬函數,在CWnd和CFrameWnd之中都有定義。由於this指針所指對象的緣故,這裏應該調用的是CFrameWnd::PreCreateWindow。該函數定義於WINFRM.CPP第521行,以下是部分代碼:

     BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
     {
         if (cs.lpszClass == NULL)
         {
             VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
             cs.lpszClass = _afxWndFrameOrView;   // COLOR_WINDOW background
         }
         ...
     }

     其中AfxDeferRegisterClass是一個定義於AFXIMPL.H中的宏。該宏如下:

         #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)

     注:這裏有宏和《深入淺出MFC》的不一樣,以上代碼是從Visual C++ 6.0摘取。

     AfxEndDeferRegisterClass定義於WINCORE.CPP第3619行,該函數很複雜,主要是註冊五個窗口類(哇!終於看到窗口類了,怎麼用5個呢?我還不清楚),不同類的PreCreateWindow成員函數都是在窗口產生之前一刻被調用,準備用來註冊窗口類。如果我們指定的窗口類是NULL,那麼就使用系統默認類。從CWnd及其各個派生類的PreCreateWindow成員函數可以看出,整個Framework針對不同功能的窗口使用了哪些窗口類。

7、窗口顯示與更新

     CMyFrameWnd::CMyFrameWnd結束後,窗口已經誕生出來;程序流程又回到CMyWinApp::InitInstance,於是調用ShowWindow函數令窗口顯示出來,並調用UpdateWindow函數令程序送出WM_PAINT消息。在SDK程序中,消息是通過窗口函數來處理,而現在窗口函數在哪裏、又如何送到窗口函數手中呢?那要從CWinApp::Run說起了。

8、執行CWinApp::Run——程序生命的活水源頭

     在執行完CMyWinApp::InitInstance函數後,程序的腳步到了AfxWinMain函數的pApp->Run了,現在我們已知道這將執行CWinApp::Run函數,該函數定義於APPCORE.CPP第391行,下面是程序代碼:

     int CWinApp::Run()
     {
         if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
         {
             // Not launched /Embedding or /Automation, but has no main window!
             TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application./n");
             AfxPostQuitMessage(0);
         }
         return CWinThread::Run();
     }

     函數調用CWinThread::Run函數,該函數定義於THRDCORE.CPP第456行,在這裏我就不Copy出來了。函數在第480行調用了PumpMessage函數,該函數定義於THRDCORE.CPP第810行,整理後的部分代碼如下:

     BOOL CWinThread::PumpMessage()
     {
         if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
         {
             return FALSE;
         }

         // process this message
         if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
         {
             ::TranslateMessage(&m_msgCur);
             ::DispatchMessage(&m_msgCur);
         }
         return TRUE;
     }

     該函數主要操作是將消息由::DispatchMessage送到窗口函數(CWnd::DefWindowProc)中,但程序一般沒有提供任何窗口函數,但在AfxEndDeferRegisterClass中,在註冊五種窗口類之前已經指定窗口函數爲:

         wndcls.lpfnWndProc = DefWindowProc;

     雖然窗口函數被指定爲DefWindowProc成員函數(CWnd::DefWindowProc),但事實上消息並不是被唧往該處,而是一個名爲AfxWndProc的全局函數去。

9、把消息與處理函數連接在一起——Message Map機制

     到此,主窗口已經產生,等待的就是各種消息了,然後調用相應的處理函數,然而消息和處理函數怎樣連接在一起呢?MFC採用了Message Map機制(消息映射機制),提供給應用程序使用的“很方便的接口”的兩組宏,其原理我還不大清楚,在這裏也無法講解,主要用法是:先在類聲明中結合DECLARE_MESSAGE_MAP()給出處理函數,如:

     class CMyFrameWnd : public CFrameWnd
     {
     public:
         CMyFrameWnd();
         afx_msg void OnPaint();    // for WM_PAINT
         afx_msg void OnAbout();    // for WM_COMMAND (IDM_ABOUT)
         DECLARE_MESSAGE_MAP()
     }

     再在相應的.CPP文件的任何位置(當然不能在函數之內)使用BEBIN_MESSAGE_MAP()和END_MESSAGE_MAP()宏把相應的消息加入去,如:

     BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
         ON_COMMAND(IDM_ABOUT, OnAbout)
         ON_WM_PAINT()
     END_MESSAGE_MAP()

 

 

總結一下

(1) 調用CXXApp構造函數構造全局對象theApp: CXApp theApp;

(2) 調用Winmain函數完成初始化工作: 通過宏_tWinMain

(3) 初始化工作包括: 窗口類註冊、窗口產生、顯示和更新、消息循環等等

     ① 註冊窗口類:AfxEndDeferRegisterClass()  //相當於SDK裏面的RegisterClass()函數

     ② 創建窗口:CMainFrame::PreCreateWindow()  //允許我們修改窗口屬性的地方

                        CFrameWnd::Create()

     ③ 消息循環:PumpMessage()

在程序運行時CWinApp將創建一個CFrameWnd框架窗口實例,而框架窗口將創建文檔模板,然後有文檔模板創建文檔實例和視實例,並將兩者關聯。

發佈了132 篇原創文章 · 獲贊 21 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章