天天看點

MFC程式運作機制

  學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)的東西!至于消息映射的具體細節本人會另寫文章說明!

 OK,MFC不再神秘,掌握了它的來龍去脈,再看其他的MFC書籍的時候,就知道我該怎麼做?為什麼我要這樣做?起到了知其然又知其是以然的效果,這就是我所追求的技術境界。

///

MFC類結構 Visual C++發展至今,MFC類庫越來越強大,其基本層次結構如圖所示,箭頭的方向是從派生類指向基類。

MFC程式運作機制

MFC類基本層次結構 其中,CObject類是MFC提供的絕大多數類的基類。該類完成動态空間的配置設定與回收,支援一般的診斷、出錯資訊處理和文檔序列化等。

  CCmdTarget類主要負責将系統事件(消息)和視窗事件(消息)發送給響應這些事件的對象,完成消息發送、等待和派遣(排程)等工作,實作應用程式的對象之間協調運作。

  CWinApp類是應用程式的主線程類,它是從CWinThread類派生而來。CWinThread類用來完成對線程的控制,包括線程的建立、運作、終止和挂起等。

  CDocument類是文檔類,包含了應用程式在運作期間所用到的資料。

  CWnd類是一個通用的視窗類,用來提供Windows 中的所有通用特性。

  CView 是用于讓使用者通過視窗來通路文檔以及負責文檔内容的顯示。

  CFrameWnd 類是從 CWnd 繼承來的,并實作了标準的架構應用程式。

  CDialog 類用來控制對話框視窗。

  CMDIFrameWnd和CMDIChildWnd類分别用來多文檔應用程式的主架構視窗和文檔子視窗的顯示和管理。

CMiniFrameWnd類是一種簡化的架構視窗,它沒有最大化和最小化視窗按鈕,也沒有視窗系統菜單,一般很少用到它。   MFC運作機制

  在程式中,當定義一個類對象時,它會自動調用相應的構造函數。所謂"類對象",就是用該類定義的"變量",這個"變量"又稱為類的一個執行個體。例如,theApp就是類CSimpApp的一個對象。

  MFC正是利用類的這種"自動調用相應的構造函數"特性,使得WinMain()函數的調用變成了應用程式架構内部的調用,是以我們在代碼中看不到每個Windows程式所必須有的WinMain()函數。

  當應用程式運作到"CSimpApp theApp;"時,系統就會先調用基類CWinApp構造函數,進行一系列的内部初始化操作,然後自動調用CSimpApp的虛函數InitInstance(),該函數會進一步調用相應的函數來完成主視窗的構造和顯示工作。下面來看看上述程式中InitInstance的執行過程。

  首先執行的是:

m_pMainWnd = new CMainFrame;

  該語句用來建立從CFrameWnd類派生而來的使用者架構視窗CMainFrame類對象,繼而調用該類的構造函數,使得Create函數被調用,完成了視窗建立工作。

  然後執行後面兩句:

m_pMainWnd->ShowWindow(m_nCmdShow);

m_pMainWnd->UpdateWindow();

  用作視窗的顯示和更新。接下來調用:

m_pMainWnd->MessageBox("你好,歡迎進入MFC世界!");

  最後傳回TRUE,表示視窗建立成功。

  由于應用程式類CWinApp是用來調用WinMain以及執行個體的初始化,是以每一個MFC應用程式有且隻能一個這樣的應用程式類,且需要一個全局的對象執行個體,如上述程式中的theApp,當然也可換一個對象名。

  InitInstance()完成初始化工作之後,接下來就是調用基類CWinApp的成員函數Run(),執行應用程式的消息循環,即重複執行接收消息并轉發消息的工作。當Run()檢查到消息隊列為空時,将調用基類CWinApp的成員函數OnIdle進行空閑時的背景處理工作。若消息隊列為空且又沒有背景工作要處理時,則應用程式一直處于等待狀态,一直等到有消息為止。

  當程式結束後,調用基類CWinApp的成員函數ExitInstance(),完成終止應用程式的收尾工作。這就是MFC應用程式的運作機制

繼續閱讀