第3章MFC框架程序剖析
1.基于MFC的程序框架剖析
对于一个单文档应用程序,使用MFC AppWizard将自动生成5个类(假设工程名为Test):
①CAboutDlg←CDialog←CWnd←CCmdTarget←CObject;
②CMainFrame←CFrameWnd←CWnd←CCmdTarget←CObject;
③CTestApp←CWinApp←CWinThread←CCmdTarget←CObject;
④CTestDoc←CDocument←CCmdTarget←CObject;
⑤CTestView←CView←CWnd←CCmdTarget←CObject;
在CTestApp的源文件Test.cpp中定义有一个CTestApp类型的全局对象theApp:CTestApp theApp;
对于MFC程序来说,通过产生一个应用程序类的对象来标识应用程序的实例。每一个MFC程序有且仅有一个从应用程序类CWinApp派生的类。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象。该对象就表示了应用程序本身。
根据C++的特性:无论全局变量还是全局对象,程序在运行时,在加载main函数之前就已经为全局变量或全局对象分配了内存空间。对于一个全局对象来说,此时就会调用该对象的构造函数进行初始化工作。又因为CTestApp派生于CWinApp,因此theApp对象的构造函数CTestApp在调用之前会调用其父类CWinApp的构造函数,从而就把自己创建的类同MFC提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。
CWinApp类定义的源文件:\Microsoft Visual Studio\VC98\MFC\SRC\APPCORE.CPP
在CWinApp的构造函数中有如下语句:
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
…
pModuleState->m_pCurrentWinApp = this;
…
}
根据C++继承性的原理,这里this对象代表的是子类CTestApp的对象,即theApp。又,在CWinApp的定义文件中可以发现构造函数的参数lpszAppName具有默认值:CWinApp(LPCTSTR lpszAppName=NULL);
,所以在调用CWinApp的构造函数时就不用显式地去传递这个参数的值。
当程序调用了CWinApp类的构造函数,并执行了CTestApp类的构造函数,且产生了theApp对象之后,接下来就进入主函数。
MFC程序的主函数所在文件:\Microsoft Visual Studio\VC98\MFC\SRC\ APPMODUL.CPP
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
_tWinMain实际上是一个宏: #define _tWinMain WinMain
Afx前缀的函数代表应用程序框架函数。应用程序框架实际上是一套辅助生成应用程序的框架模型。在MFC中以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们。
AfxWinMain函数所在文件:\Microsoft Visual Studio\VC98\MFC\SRC\WINMAIN.CPP
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
…
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
…
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
…
}
注:①AfxGetThread函数返回的就是AfxGetApp函数的结果,且CWinApp派生于CWinThread因此pThread和pApp这两个指针是一致的。AfxGetApp函数的返回结果为:AfxGetModuleState()->m_pCurrentWinApp.
由CWinApp的构造函数中代码:pModuleState->m_pCurrentWinApp = this;可知pThread和pApp所指向的都是CTestApp类的对象,即theApp全局对象。
②InitApplication、InitInstance、Run这三个函数用来完成Win32程序所需要的几个步骤:设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环,以及窗口过程函数;
③InitApplication完成MFC内部管理方面的工作,包括窗口类的注册、创建,窗口的显示和更新;而InitInstance函数在CWinApp中定义为一个虚函数,同时在CTestApp中也有一个InitInstance函数,根据多态性原理可知AfxWinMain实际调用的是CTestApp的InitInstance函数:
BOOL CTestApp::InitInstance()
{
…
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
…
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
2.MFC框架窗口
①设计和注册窗口:
CMainFrame类的对象代表应用程序框架窗口。该类有一个PreCreateWindow函数,该函数在窗口产生之前被调用,其实现代码如下:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
…
}
可见其调用了父类的PreCreateWindow函数。
CFrameWnd实现所在文件::
C:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC\WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
}
…
}
AfxDeferRegisterClass其实是一个宏:
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
AfxEndDeferRegisterClass即是用来完成窗口类的注册,该函数定义位于WINCORE.CPP文件中:
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
…
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
…
if (fToRegister & AFX_WND_REG)
{
…
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WND_REG;
}
if (fToRegister & AFX_WNDOLECONTROL_REG)
{
…
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
}
…
}
AfxEndDeferRegisterClass函数首先判断窗口类的类型,然后赋予其相应的类名,这些类名都是MFC预先定义的,之后调用AfxRegisterClass函数注册窗口类。该函数也位于WINCORE.CPP文件中:
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
…
if (!::RegisterClass(lpWndClass))
{
TRACE1("Can't register window class named %s\n",
lpWndClass->lpszClassName);
return FALSE;
}
…
}
可见在AfxRegisterClass函数内部最终还是通过RegisterClass实现窗口类的注册。
注:wndcls.lpfnWndProc = DefWindowProc;指定了一个默认的窗口过程。但是MFC并不是把所有消息都交给DefWindowProc这一默认窗口过程来处理,而是采用了一种称之为“消息映射”的机制来处理各种消息。
②创建窗口:
在CFrameWnd类的LoadFrame函数中会调用CFrameWnd::Create函数,而在Create函数内部则调用CWnd::CreateEx函数(CWnd::CreateEx函数不是虚函数)实现窗口创建功能。该函数实现代码位于WINCORE.CPP文件中。
在CreateEx函数实现中又再次调用了PreCreateWindow函数,因为PreCreateWindow是一个虚函数,所以这里实际上调用的是子类即CMainFrame类的函数。之所以在这里再次调用这个函数,主要是为了在产生窗口之前让程序员有机会修改窗口外观。
③显示窗口和更新窗口
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
④消息循环
CWinThread类的Run函数用来完成消息循环这一任务。在AfxWinMain中调用如下:pThread->Run();
Run函数定义位于THRDCORE.CPP文件中:
int CWinThread::Run()
{
…
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{…}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
…
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
…
}
该函数的主要结构是一个for循环,该循环在接收到一个WM_QUIT消息时退出。在此循环中调用了PumpMessage函数,该函数实现如下:
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
…
return FALSE;
}
…
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
可见其内部实现与Win32 SDK编程的消息处理代码是一致的。
文档/视图结构:MFC程序除了主框架窗口以外,还有一个窗口是视类窗口,对应的类是CView类。MFC提供了一个文档/视图结构,其中文档就是指CDocument类,视图就是指CView类。数据的存储和加载由文档类来完成,数据的显示和修改则由视类来完成,从而把数据管理和显示方法分离开来。
3.窗口类、窗口类对象、窗口
窗口类对象与窗口并不是一回事,它们之间唯一的关系是窗口类内部定义了一个窗口句柄变量,保存了与这个窗口类对象相关的那个窗口的句柄。窗口销毁时与之对应的窗口类对象销毁与否要看其生命周期是否结束,但窗口类对象销毁时,与之相关的窗口也将销毁。