看過候捷(JJHOU)老師的《深入淺出MFC》的,對它一定很熟悉。呵呵,本文是獻給沒有看過那本書,但是又很希望學習MFC程序設計的朋友的。(沒有看過那本書的朋友還不趕快去買?)其實本文,主要是對《深入淺出MFC》第六章的一個總結和補充罷了?。ū疚挠性摃煌牡胤?,也有一些筆者自己的見解?。?br>言歸正傳。
假如你用AppWizard一步一步NEXT下來,然后在CLASSVIEW中去找尋WINMAIN函數(shù),那么你只有失望。MFC最大的特點是什么?封裝!MFC的確封裝的太好了,以至于很多想學習MFC的人都望而卻步。閑話少說,還是繼續(xù)我們今天的話題,MAIN函數(shù)!實話告訴你吧,即使你搜索所有的MFC生成的文件,都無法發(fā)現(xiàn)WINMAIN的字眼,那么它就近在什么地方呢?
我相信你已經(jīng)想到,MAIN函數(shù)應該在主要的應用程序文件中。難道是“您定義的程序名.cpp”這個文件?不錯就是它。再Crtl+F一下,看有沒有我們要找的WINMAIN函數(shù)?看來你又要失望了,但是你注意有這樣一句:
/////////////////////////////////////////////////////////////////////////////
// The one and only CMyApp object
CMyApp theApp;
是不是很特別,再注意一下那句注釋“The one and only CMyAppobject”,每個應用程序有且只用一個CMyApp對象。我想你應該想到了,WinMain函數(shù)每個程序也只能有一個,那么這個全局對象跟WinMain函數(shù)肯定有莫大的關系?沒錯,相信你的直覺。
特別注意:深曉C++細節(jié)的人一定知道,全局對象優(yōu)先于MAIN函數(shù)執(zhí)行的道理。如果你不知道也沒關系,那么我在這里告訴你:“全局對象優(yōu)先于MIAN函數(shù)執(zhí)行,且構建于棧中,切記,切記!”
現(xiàn)在,我們該深入WinMain運行機制了,確切的說,應該是MFC的機制!
首先,看看MFC的庫文件把,它能給我們帶來許多驚喜。(vc6的相應的目錄是\Microsoft VisualStudio\VC98\MFC\SRC;VC7相應的目錄是\Microsoft Visual Studio .NET2003\Vc7\atlmfc\src\mfc)
現(xiàn)在我們就從這個全局下手,開始今天的旅途。
CMyApp theApp;
此時,系統(tǒng)會執(zhí)行CMyApp的父類(CWinApp)構造函數(shù),再執(zhí)行CMyApp的構造函數(shù)。(先有老爹,再有兒子!),此時就會調(diào)用CWinApp的構造函數(shù)。
CWinApp的構造函數(shù)(在VC提供的MFC代碼中以“文中的一個字或詞組”的方式查詢關鍵字,此時打開APPCORE.CPP,以下使用相同搜索方式,不再復述。)找到以下內(nèi)容:
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
... ...
}
OK,就到這里就可以了,仔細看上面代碼,它已經(jīng)完成了應用程序線程額的啟動,它給予了我們程序的生命?,F(xiàn)在請注意:
pModuleState->m_pCurrentWinApp = this;
構造完父類,現(xiàn)在構造子類。可是我們看到,AppWizard給我們的子類里它什么也沒做?是的,這一切都聽從你的安排!
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
接下來就是今天的主角兒了,搜索關鍵字“WinMain”,出現(xiàn)很多文件。別急,因為現(xiàn)在我們應該先看看WinMain的聲明。打開appmodul.cpp:
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine,nCmdShow);
}
這里_tWinMain是為了支持UNICODE而命名的一個宏,真正起作用的是AfxWinMain,注意看看它的參數(shù),是不是和SDK的WinMain函數(shù)一樣?
現(xiàn)在再搜索下AfxWinMain,其實在winmain.cpp中:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCEhPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine,nCmdShow))
// App global initializations (rare)
if (pApp != NULL &&!pApp->InitApplication())
// Perform specific initializations
if (!pThread->InitInstance())
{
}
nReturnCode = pThread->Run();
... ...
}
此段代碼注意五個細節(jié):
CWinApp* pApp = AfxGetApp();
意為獲得對象指針,其實就是剛才那個THIS。不記得了?指向CMyApp的那個!還值得注意的是,Afx意是全局的,隨時你都可以調(diào)用它。(AFX就是MFC開發(fā)小組的開發(fā)代號,意為ApplicationFramework 傳說X只是為了好看,沒實在意思?!)
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine,nCmdShow))
AfxWinInit完成了線程的初始化和窗框類的注冊。具體參看appinit.cpp中的定義。
if (pApp != NULL &&!pApp->InitApplication())
其實pApp和pThread是同一個指針,都是指向CMyApp的指針,這里因為CMyApp中沒有定義InitApplication,實際上就調(diào)用的CWinApp::InitApplication(),完成了MFC的內(nèi)容管理。
if (!pThread->InitInstance())
因為CMyApp中改寫了它,所以調(diào)用CMyApp中的,其實它也是初始化工作。此時也完成了默認窗口類的定義。假如你熟悉SDK編程的話,一定不會忘記窗口類的設計、注冊、創(chuàng)建、現(xiàn)實及更新的步驟,此時MFC以為你設計好了默認的窗口類。
現(xiàn)在你不禁要疑問,InitApplication()和InitInstance()有何不同?
答案是,假如你執(zhí)行一個程序,于是兩個函數(shù)都會被調(diào)用;當你在不關閉前一個程序的前提下,再執(zhí)行一個程序,那么就只執(zhí)行后一個函數(shù)。
nReturnCode = pThread->Run();
這個一步驟在《深入淺出MFC》中被成為程序的活水源頭,在我看來它就是你開車踩油門的步驟。待會我們會具體闡述!
在設計窗口類以后,就應該是注冊,MFC自動調(diào)用(跳轉(zhuǎn)到)AfxEndDeferRegisterClass
在窗口的注冊以后,就應該是窗口的創(chuàng)建工作,此時會調(diào)用CFrameWnd::Create(),該代碼位于WINFRM.Cpp中
BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
}
m_strTitle =lpszWindowName;
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName,dwStyle,
{
}
return TRUE;
}
其中完成了窗口的創(chuàng)建工作,里面還涉及擴展風格的調(diào)用CreateEx,具體細節(jié)請參看MSDN。
此時你不禁要問,我們的事兒都讓MFC做完了?工業(yè)化生產(chǎn)出來的窗口都是千篇一律啊,我要有我自己的風格!
別急,MFC給用戶提供了一個修改窗口設計的機會那就是:PreCreateWindow(CREATESTRUCT&cs)你在MSDN中查詢一下CREATESTRUCT這個結構體,你會發(fā)現(xiàn)它和我們的CreateWindow幾乎是一模一樣,這個就是MFC留給你修改窗口的一個機會。在PreCreateWindow時,會跳到CWnd::PreCreateWindow,里面有一個宏:AfxDeferRegisterClass,它的作用是:如果該窗口類沒有被注冊,那么就注冊它;如果注冊了,就什么也不管!
窗口類的設計、注冊、創(chuàng)建都已經(jīng)完成,現(xiàn)在只剩下更新和顯示了。這些工作都交由CMyApp::InitInstance()完成:
現(xiàn)在if(!pThread->InitInstance())的工作已經(jīng)完成,按照MAIN函數(shù)的內(nèi)容,接下來該:nReturnCode= pThread->Run()了
此時應該調(diào)用CMyApp的Run()函數(shù),但是在CMyApp類中,根本沒有聲明或定義這樣一個函數(shù),根據(jù)多態(tài)性的原來,指針遷升,指向CWinApp::Run(),其代碼位于APPCORE.CPP中:
int CWinApp::Run()
{
}
最后你會發(fā)現(xiàn),它由調(diào)用了一個CWinThread::Run(),此時你就看不到CWinThread::Run()的代碼了(至少筆者沒有找到,因為微軟只提供了部分MFC代碼。)但是你可以在MSDN中找到CWinThread::Run()的描述:
Run 控制線程的函數(shù)。包含消息泵。一般不重寫。
再具體點就是:
Run acquires and dispatches Windows messages until the applicationreceives a WM_QUIT message. If the thread's message queue currentlycontains no messages, Run calls OnIdle to perform idle-timeprocessing. Incoming messages go to the PreTranslateMessage memberfunction for special processing and then to the Windows functionTranslateMessage for standard keyboard translation. Finally, theDispatchMessage Windows function is called.
Run is rarely overridden, but you can override it to implementspecial behavior.
This member function is used only in user-interface threads.
原來它把消息循環(huán)包裝了一下,在MFC中稱為消息映射(messagemap)的東西!至于消息映射的具體細節(jié)本人會另寫文章說明!
OK,MFC不再神秘,掌握了它的來龍去脈,再看其他的MFC書籍的時候,就知道我該怎么做?為什么我要這樣做?起到了知其然又知其所以然的效果,這就是我所追求的技術境界。