MFC程式運行機制

來源:互聯網
上載者:User
學MFC,竟然還不知道MFC的MAIN函數在什麼地方?怎麼啟動並執行?實在不高明。
看過候捷(JJHOU)老師的《深入淺出MFC》的,對它一定很熟悉。呵呵,本文是獻給沒有看過那本書,但是又很希望學習MFC程式設計的朋友的。(沒有看過那本書的朋友還不趕快去買?)其實本文,主要是對《深入淺出MFC》第六章的一個總結和補充罷了!(本文有該書不同的地方,也有一些筆者自己的見解!)
言歸正傳。
假如你用AppWizard一步一步NEXT下來,然後在CLASSVIEW中去找尋WINMAIN函數,那麼你只有失望。MFC最大的特點是什嗎?封裝!MFC的確封裝的太好了,以至於很多想學習MFC的人都望而卻步。閑話少說,還是繼續我們今天的話題,MAIN函數!實話告訴你吧,即使你搜尋所有的MFC產生的檔案,都無法發現WINMAIN的字眼,那麼它就近在什麼地方呢?
我相信你已經想到,MAIN函數應該在主要的應用程式檔案中。難道是“您定義的程式名.cpp”這個檔案?不錯就是它。再Crtl+F一下,看有沒有我們要找的WINMAIN函數?看來你又要失望了,但是你注意有這樣一句:

/////////////////////////////////////////////////////////////////////////////
// The one and only CMyApp object

CMyApp theApp; //本人建立的工程名為My。

是不是很特別,再注意一下那句注釋“The one and only CMyApp object”,每個應用程式有且只用一個CMyApp對象。我想你應該想到了,WinMain函數每個程式也只能有一個,那麼這個全域對象跟WinMain函數肯定有莫大的關係?沒錯,相信你的直覺。
特別注意:深曉C++細節的人一定知道,全域對象優先於MAIN函數執行的道理。如果你不知道也沒關係,那麼我在這裡告訴你:“全域對象優先於MIAN函數執行,且構建於棧中,切記,切記!”
現在,我們該深入WinMain運行機制了,確切的說,應該是MFC的機制!
首先,看看MFC的庫檔案把,它能給我們帶來許多驚喜。(vc6的相應的目錄是\Microsoft Visual Studio\VC98\MFC\SRC;VC7相應的目錄是\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\mfc)
現在我們就從這個全域下手,開始今天的旅途。
CMyApp theApp;
此時,系統會執行CMyApp的父類(CWinApp)建構函式,再執行CMyApp的建構函式。(先有老爹,再有兒子!),此時就會調用CWinApp的建構函式。

CWinApp的建構函式(在VC提供的MFC代碼中以“文中的一個字或片語”的方式查詢關鍵字,此時開啟APPCORE.CPP,以下使用相同搜尋方式,不再複述。)找到以下內容:
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;

// initialize CWinThread state
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();

// initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
... ...
}
OK,就到這裡就可以了,仔細看上面代碼,它已經完成了應用程式線程額的啟動,它給予了我們程式的生命。現在請注意:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this;
這兩行代碼其實都是做的一件事兒。
這段代碼的意思是,獲得了CMyApp的全域對象的this指標。(此時你肯定要疑問,為什麼是CMyApp的指標?this目前是在CWinApp中啊? 對此我的答案是,可是你是由CMyApp的對象引發的CWinApp的構造啊!!)這個指標可非一般的人物,稍後我們的很多工作都要靠它完成。
CWinApp之中的成員變數將因為theApp這個全域對象的誕生而獲得配置和初始值。
構造完父類,現在構造子類。可是我們看到,AppWizard給我們的子類裡它什麼也沒做?是的,這一切都聽從你的安排!
CMyApp::CMyApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

接下來就是今天的主角兒了,搜尋索引鍵“WinMain”,出現很多檔案。別急,因為現在我們應該先看看WinMain的聲明。開啟appmodul.cpp:

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

這裡_tWinMain是為了支援UNICODE而命名的一個宏,真正起作用的是AfxWinMain,注意看看它的參數,是不是和SDK的WinMain函數一樣?
現在再搜尋下AfxWinMain,其實在winmain.cpp中:

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
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))
goto InitFailure;

// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;

// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
... ...
}
此段代碼注意五個細節:
CWinApp* pApp = AfxGetApp();
意為獲得對象指標,其實就是剛才那個THIS。不記得了?指向CMyApp的那個!還值得注意的是,Afx意是全域的,隨時你都可以調用它。(AFX就是MFC開發小組的開發代號,意為Application Framework 傳說X只是為了好看,沒實在意思?!)
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
AfxWinInit完成了線程的初始化和窗框類的註冊。具體參看appinit.cpp中的定義。
if (pApp != NULL && !pApp->InitApplication())
其實pApp和pThread是同一個指標,都是指向CMyApp的指標,這裡因為CMyApp中沒有定義InitApplication,實際上就調用的CWinApp::InitApplication(),完成了MFC的內容管理。
if (!pThread->InitInstance())
因為CMyApp中改寫了它,所以調用CMyApp中的,其實它也是初始化工作。此時也完成了預設視窗類別的定義。假如你熟悉SDK編程的話,一定不會忘記視窗類別的設計、註冊、建立、現實及更新的步驟,此時MFC以為你設計好了預設的視窗類別。
現在你不禁要疑問,InitApplication()和InitInstance()有何不同?
答案是,假如你執行一個程式,於是兩個函數都會被調用;當你在不關閉前一個程式的前提下,再執行一個程式,那麼就只執行後一個函數。
nReturnCode = pThread->Run();
這個一步驟在《深入淺出MFC》中被成為程式的活水源頭,在我看來它就是你開車踩油門的步驟。待會我們會具體闡述!

在設計視窗類別以後,就應該是註冊,MFC自動調用(跳轉到)AfxEndDeferRegisterClass(WINCORE.CPP中),為你註冊了五個視窗類別,分別是:AfxWnd,AfxCreateBar,AfxMDIFrame,AfxFrameOrView,AfxOleControl以上視窗類別MFC將自動轉化成獨立無二的類名,供其調用。
在視窗的註冊以後,就應該是視窗的建立工作,此時會調用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)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}

m_strTitle = lpszWindowName; // save title for later

if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}

return TRUE;
}

其中完成了視窗的建立工作,裡面還涉及擴充風格的調用CreateEx,具體細節請參看MSDN。

此時你不禁要問,我們的事兒都讓MFC做完了?工業化生產出來的視窗都是千篇一律啊,我要有我自己的風格!
別急,MFC給使用者提供了一個修改視窗設計的機會那就是:PreCreateWindow(CREATESTRUCT& cs) 你在MSDN中查詢一下CREATESTRUCT這個結構體,你會發現它和我們的CreateWindow幾乎是一模一樣,這個就是MFC留給你修改視窗的一個機會。在PreCreateWindow時,會跳到CWnd::PreCreateWindow,裡面有一個宏:AfxDeferRegisterClass,它的作用是:如果該視窗類別沒有被註冊,那麼就註冊它;如果註冊了,就什麼也不管!
視窗類別的設計、註冊、建立都已經完成,現在只剩下更新和顯示了。這些工作都交由 CMyApp::InitInstance()完成:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
現在if (!pThread->InitInstance())的工作已經完成,按照MAIN函數的內容,接下來該:nReturnCode = pThread->Run()了
此時應該調用CMyApp的Run()函數,但是在CMyApp類中,根本沒有聲明或定義這樣一個函數,根據多態性的原來,指標遷升,指向CWinApp::Run(),其代碼位於APPCORE.CPP中:

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

最後你會發現,它由調用了一個CWinThread::Run(),此時你就看不到CWinThread::Run()的代碼了(至少筆者沒有找到,因為微軟只提供了部分MFC代碼。)但是你可以在MSDN中找到CWinThread::Run()的描述:
Run 控制線程的函數。包含訊息泵。一般不重寫。
再具體點就是:
Run acquires and dispatches Windows messages until the application receives a WM_QUIT message. If the thread's message queue currently contains no messages, Run calls OnIdle to perform idle-time processing. Incoming messages go to the PreTranslateMessage member function for special processing and then to the Windows function TranslateMessage for standard keyboard translation. Finally, the DispatchMessage Windows function is called.
Run is rarely overridden, but you can override it to implement special behavior.
This member function is used only in user-interface threads.
原來它把訊息迴圈封裝了一下,在MFC中稱為訊息映射(message map)的東西!

                                                  好文

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.