这一节鸡啄米就为大家分析下MFC应用程序框架的运行流程。
一.SDK应用程序与MFC应用程序运行过程的对比
程序运行都要有入口函数,在之前的C++教程中都是main函数,而Windows应用程序的入口函数是WinMain函数,MFC程序也是从WinMain函数开始的。下面鸡啄米就给出用Windows SDK写的“HelloWorld”程序,与MFC应用程序框架进行对比,这样能更好的了解框架是怎样运行的。Windows SDK开发程序就是不使用MFC类库,直接用Windows API函数进行软件开发。鸡啄米不是要讲解SDK开发,只是为了对比而简单介绍,至于SDK开发可以在大家学完MFC以后选择是否要研究,一般来说有简单了解就可以了。
SDK应用程序
首先,给出Windows SDK应用程序“HelloWorld”的源码:
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函数,窗口过程函数处理完消息就完成了某项操作或任务。本例是要显示“HELLO WORLD”字符串,UpdateWindow函数会发送WM_PAINT消息,但是此消息不经过消息队列而是直接送到窗口过程处理,在窗口过程函数中最终绘制了“HELLO WORLD”字符串。
MFC应用程序
下面是MFC应用程序的运行流程,通过MFC库中代码进行分析:
首先在HelloWorld.cpp中定义全局对象theApp:CHelloWorldApp theApp;。调用CWinApp和CHelloWorldApp的构造函数后,进入WinMain函数(位于appmodul.cpp中)。
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中)。
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函数的代码如下:
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函数的代码如下:
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形式如下:
LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam) { …… CWnd*pWnd=CWnd::FromHandlePermanent(hWnd); ReturnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam); }
两者运行流程对比
到此,通过对比可以发现,MFC应用程序的运行流程与SDK程序是类似的,都是先进行一些初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消息都由窗口过程函数处理。现在大家是不是觉得有些头绪了?在运行流程上有基本的掌握即可。
二.MFC应用程序框架主要类之间的关系
给大家演示了如何利用应用程序向导生成单文档应用程序框架,可以看到程序的基本框架和必要的代码都自动生成了,上一讲又讲解了文件组成结构,实际上在前面自动生成的框架中比较重要的类包括以下几个:CHelloWorldApp、CMainFrame、CHelloWorldDoc和CHelloWorldView,至于其他的类比如CClassView、CFileView等都是在框架窗口(CMainFrame)上创建的面板等,不是必要的。
鸡啄米就四个主要类的关系简单讲下,CHelloWorldApp类处理消息,将收到的消息分发给相应的对象。CMainFrame是视图CHelloWorldView的父窗口,视图CHelloWorldView就显示在CMainFrame的客户区中。视图类CHelloWorldView用来显示文档类CHelloWorldDoc中的数据,并根据对视图类的操作修改文档类的数据。一个视图类只能跟一个文档类相联系,而一个文档类可以跟多个视图类相联系。关于视图类和文档类的关系后面会详细讲解。
本节VC++/MFC编程入门教程内容比较多,主要是让大家对MFC应用程序的运行原理有大概的了解。对于以后的MFC开发有很多好处。
我们看到PumpMessage中通过调用GetMessage、TranslateMessage、DispatchMessage等建立了消息循环并投递消息。
窗口过程函数AfxWinProc形式如下: