上一講雞啄米講的是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函數註冊窗口類->調用ShowWindow和UpdateWindow函數顯示並更新窗口->進入消息循環。關於消息循環再簡單說下,Windows應用程序是消息驅動的,系統或用戶讓應用程序進行某項操作或完成某個任務時會發送消息,進入程序的消息隊列,然後消息循環會將消息隊列中的消息取出,交予相應的窗口過程處理,此程序的窗口過程函數就是myWndProc函數,窗口過程函數處理完消息就完成了某項操作或任務。本例是要顯示“HELLOWORLD”字符串,UpdateWindow函數會發送WM_PAINT消息,但是此消息不經過消息隊列而是直接送到窗口過程處理,在窗口過程函數中最終繪製了“HELLO WORLD”字符串。
MFC應用程序
下面是MFC應用程序的運行流程,通過MFC庫中代碼進行分析:
首先在HelloWorld.cpp中定義全局對象theApp:CHelloWorldApp theApp;。調用CWinApp和CHelloWorldApp的構造函數後,進入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函數又調用了CMainFrame的LoadFrame函數註冊並創建了窗口,執行完ProcessShellCommand函數以後,調用了m_pMainWnd的ShowWindow和UpdateWindow函數顯示並更新框架窗口。這些是不是與上面的SDK程序十分類似?
接下來該是消息循環了,上面的AfxWinMain函數中調用了pThread的Run函數(位於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中通過調用GetMessage、TranslateMessage、DispatchMessage等建立了消息循環並投遞消息。
窗口過程函數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應用程序框架主要類之間的關係
在第二講中,給大家演示瞭如何利用應用程序嚮導生成單文檔應用程序框架,可以看到程序的基本框架和必要的代碼都自動生成了,上一講又講解了文件組成結構,實際上在前面自動生成的框架中比較重要的類包括以下幾個:CHelloWorldApp、CMainFrame、CHelloWorldDoc和CHelloWorldView,至於其他的類比如CClassView、CFileView等都是在框架窗口(CMainFrame)上創建的面板等,不是必要的。
雞啄米就四個主要類的關係簡單講下,CHelloWorldApp類處理消息,將收到的消息分發給相應的對象。CMainFrame是視圖CHelloWorldView的父窗口,視圖CHelloWorldView就顯示在CMainFrame的客戶區中。視圖類CHelloWorldView用來顯示文檔類CHelloWorldDoc中的數據,並根據對視圖類的操作修改文檔類的數據。一個視圖類只能跟一個文檔類相聯繫,而一個文檔類可以跟多個視圖類相聯繫。關於視圖類和文檔類的關係後面會詳細講解。