MFC運行程序順序

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內部初始化操作,處理很多難以預料的問題,它與AfxWinTerm()對應。該函數定義於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()

    爲什麼經過這樣的宏之後,消息就會自動流往指定的函數去呢?謎底在於Message Map的結構設計,自己找《深入淺出MFC》第3章的Message Map仿真程序去啃一啃吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章