VS2010/MFC編程入門之三(MFC應用程序框架分析)

上一講雞啄米講的是VS2010應用程序工程中文件的組成結構,可能大家對工程的運行原理還是很模糊,理不出頭緒,畢竟跟C++編程入門系列中的例程差別太大。這一節雞啄米就爲大家分析下MFC應用程序框架的運行流程。

       .SDK應用程序與MFC應用程序運行過程的對比

       程序運行都要有入口函數,在之前的C++教程中都是main函數,而Windows應用程序的入口函數是WinMain函數,MFC程序也是從WinMain函數開始的。下面雞啄米就給出用Windows SDK寫的“HelloWorld”程序,與應用程序框架進行對比,這樣能更好的瞭解框架是怎樣運行的。Windows SDK開發程序就是不使用MFC類庫,直接用WindowsAPI函數進行軟件開發。雞啄米不是要講解SDK開發,只是爲了對比而簡單介紹,至於SDK開發可以在大家學完MFC以後選擇是否要研究,一般來說有簡單瞭解就可以了。

       SDK應用程序

       首先,給出WindowsSDK應用程序“HelloWorld”的源碼:  

C++代碼

1.  #include <windows.h>   

2.    

3.  LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);   

4.      

5.  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)     

6.  {     

7.    const static TCHAR appName[] = TEXT("Hello world");      

8.    WNDCLASSEX myWin;     

9.    myWin.cbSize = sizeof(myWin);      

10.   myWin.style = CS_HREDRAW | CS_VREDRAW;     

11.   myWin.lpfnWndProc = myWndProc;     

12.   myWin.cbClsExtra = 0;     

13.   myWin.cbWndExtra = 0;     

14.   myWin.hInstance = hInstance;     

15.   myWin.hIcon = 0;     

16.   myWin.hIconSm  = 0;     

17.   myWin.hCursor = 0;     

18.   myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);      

19.   myWin.lpszMenuName = 0;     

20.   myWin.lpszClassName = appName;     

21.   //Register      

22.   if (!RegisterClassEx(&myWin)) return 0;      

23.   const HWND hWindow = CreateWindow(     

24.     appName,     

25.     appName,     

26.     WS_OVERLAPPEDWINDOW,     

27.     CW_USEDEFAULT,     

28.     CW_USEDEFAULT,     

29.     CW_USEDEFAULT,     

30.     CW_USEDEFAULT,     

31.     0,     

32.     0,     

33.     hInstance,     

34.     0);     

35.   ShowWindow(hWindow,iCmdShow);     

36.   UpdateWindow(hWindow);     

37.   {      

38.     MSG msg;     

39.     while(GetMessage(&msg,0,0,0))      

40.     {     

41.       TranslateMessage(&msg);     

42.       DispatchMessage(&msg);     

43.     }     

44.     return (int)msg.wParam;      

45.   }      

46. }      

47.      

48. LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)     

49. {      

50.   if (msg==WM_PAINT)     

51.   {      

52.     PAINTSTRUCT ps;     

53.     const HDC hDC = BeginPaint(hWindow,&ps);     

54.     RECT rect;     

55.     GetClientRect(hWindow,&rect);     

56.     DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);     

57.     EndPaint(hWindow,&ps);     

58.     return 0;      

59.   }      

60.   else if (msg==WM_DESTROY)      

61.   {      

62.     PostQuitMessage(0);     

63.     return 0;      

64.   }      

65.   return DefWindowProc(hWindow,msg,wParam,lParam);     

66. }  

       上面的程序運行的流程是:進入WinMain函數->初始化WNDCLASSEX,調用RegisterClassEx函數註冊窗口類->調用ShowWindowUpdateWindow函數顯示並更新窗口->進入消息循環。關於消息循環再簡單說下,Windows應用程序是消息驅動的,系統或用戶讓應用程序進行某項操作或完成某個任務時會發送消息,進入程序的消息隊列,然後消息循環會將消息隊列中的消息取出,交予相應的窗口過程處理,此程序的窗口過程函數就是myWndProc函數,窗口過程函數處理完消息就完成了某項操作或任務。本例是要顯示“HELLOWORLD”字符串,UpdateWindow函數會發送WM_PAINT消息,但是此消息不經過消息隊列而是直接送到窗口過程處理,在窗口過程函數中最終繪製了“HELLO WORLD”字符串。

      MFC應用程序

       下面是MFC應用程序的運行流程,通過MFC庫中代碼進行分析:

       首先在HelloWorld.cpp中定義全局對象theAppCHelloWorldApp theApp;。調用CWinAppCHelloWorldApp的構造函數後,進入WinMain函數(位於appmodul.cpp中)。

C++代碼

1.  extern "C" int WINAPI  

2.  _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,  

3.      _In_ LPTSTR lpCmdLine, int nCmdShow)  

4.  #pragma warning(suppress: 4985)  

5.  {  

6.      // call shared/exported WinMain  

7.      return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);  

8.  }  

       TCHAR.h中,有此定義:#define_tWinMain   WinMain,所以這裏的_tWinMain就是WinMain函數。它調用了AfxWinMain函數(位於WinMain.cpp中)。

C++代碼

1.  int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)  

2.  {   

3.         .............略   

4.         // App global initializations (rare)  

5.         if (pApp != NULL && !pApp->InitApplication())  

6.                goto InitFailure;   

7.    

8.         if (!pThread->InitInstance())   

9.         {  

10.               .........略   

11.        }  

12.   

13.        // Run函數位於THRDCORE.cpp中,由此函數進入消息循環   

14.        nReturnCode = pThread->Run();  

15.   

16.        ..............略   

17.   

18.        return nReturnCode;   

19. }   

       上面InitInstance函數的代碼如下:

C++代碼

1.  BOOL CTestApp::InitInstance()       

2.  {      

3.         .............略       

4.         CSingleDocTemplate* pDocTemplate;      

5.         pDocTemplate = new CSingleDocTemplate(       

6.                IDR_MAINFRAME,      

7.                RUNTIME_CLASS(CTestDoc),      

8.                RUNTIME_CLASS(CMainFrame),      // main SDI frame window      

9.                RUNTIME_CLASS(CTestView));    

10.        if (!pDocTemplate)   

11.             return FALSE;     

12.        AddDocTemplate(pDocTemplate);      

13.        // Parse command line for standard shell commands, DDE, file open      

14.       

15.        CCommandLineInfo cmdInfo;      

16.        ParseCommandLine(cmdInfo);      

17.       

18.        //ProcessShellCommand位於AppUI2.cpp中,註冊並創建窗口       

19.        if (!ProcessShellCommand(cmdInfo))      

20.              return FALSE;       

21.       

22.        m_pMainWnd->ShowWindow(SW_SHOW);      

23.        m_pMainWnd->UpdateWindow();      

24.       

25.        return TRUE;       

26. }      

       InitInstance中的ProcessShellCommand函數又調用了CMainFrameLoadFrame函數註冊並創建了窗口,執行完ProcessShellCommand函數以後,調用了m_pMainWndShowWindowUpdateWindow函數顯示並更新框架窗口。這些是不是與上面的SDK程序十分類似?

       接下來該是消息循環了,上面的AfxWinMain函數中調用了pThreadRun函數(位於THRDCORE.cpp中),在Run中包含了消息循環。Run函數的代碼如下:

C++代碼

1.  int CWinThread::Run()      

2.  {      

3.          .............略       

4.          // phase2: pump messages while available      

5.          do      

6.          {      

7.                // pump message, but quit on WM_QUIT      

8.                if (!PumpMessage())       

9.                       return ExitInstance();       

10.       

11.               // reset "no idle" state after pumping "normal" message      

12.               if (IsIdleMessage(&m_msgCur))      

13.               {      

14.                      bIdle = TRUE;      

15.       

16.                      lIdleCount = 0;      

17.       

18.               }      

19.        } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));      

20.        ..............略       

21. }       

22.         

23. BOOL CWinThread::PumpMessage()      

24. {     

25.        return AfxInternalPumpMessage();    

26. }    

27.   

28. BOOL AFXAPI AfxInternalPumpMessage()   

29. {   

30.        _AFX_THREAD_STATE *pState = AfxGetThreadState();  

31.       

32.        if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))         

33.        {      

34.              .............略       

35.        }      

36.        ...............略       

37.        if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))  

38.        {  

39.             ::TranslateMessage(&(pState->m_msgCur));   

40.             ::DispatchMessage(&(pState->m_msgCur));   

41.        }    

42.       

43.        return TRUE;       

44. }       

       我們看到PumpMessage中通過調用GetMessageTranslateMessageDispatchMessage等建立了消息循環並投遞消息。

       窗口過程函數AfxWinProc形式如下:

C++代碼

1.  LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)   

2.  {  

3.        ……  

4.        CWnd*pWnd=CWnd::FromHandlePermanent(hWnd);  

5.        ReturnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);  

6.  }  

       兩者運行過程對比

       到此,通過對比可以發現,MFC應用程序的運行流程與SDK程序是類似的,都是先進行一些初始化過程,再註冊並創建窗口,然後顯示、更新窗口,最後進入消息循環,消息都由窗口過程函數處理。現在大家是不是覺得有些頭緒了?在運行流程上有基本的掌握即可。

       .MFC應用程序框架主要類之間的關係

       在第二講中,給大家演示瞭如何利用應用程序嚮導生成單文檔應用程序框架,可以看到程序的基本框架和必要的代碼都自動生成了,上一講又講解了文件組成結構,實際上在前面自動生成的框架中比較重要的類包括以下幾個:CHelloWorldAppCMainFrameCHelloWorldDocCHelloWorldView,至於其他的類比如CClassViewCFileView等都是在框架窗口(CMainFrame)上創建的面板等,不是必要的。

       雞啄米就四個主要類的關係簡單講下,CHelloWorldApp類處理消息,將收到的消息分發給相應的對象。CMainFrame是視圖CHelloWorldView的父窗口,視圖CHelloWorldView就顯示在CMainFrame的客戶區中。視圖類CHelloWorldView用來顯示文檔類CHelloWorldDoc中的數據,並根據對視圖類的操作修改文檔類的數據。一個視圖類只能跟一個文檔類相聯繫,而一個文檔類可以跟多個視圖類相聯繫。關於視圖類和文檔類的關係後面會詳細講解。
發佈了3 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章