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仿真程序去啃一啃吧!