主线:设计窗口类à注册窗口类à产生窗口à显示窗口à更新窗口à消息循环(将消息路由到窗口中去处理)。
APPMODUL.CPP源文件被编译链接进入项目,从APPMODUL.CPP中的_tWinMain函数入口进入,开始进行编译。通过Go to Defination of_tWinMain可以看到_tWinMain为:#define_tWinMainWinMain宏。在_tWinApp中是通过AfxWinMain函数完成WinMian入口函数的功能。带有Afx前缀的函数属于应用程序框架的函数。应用程序框架是一套辅助生成应用程序的框架模型。框架模型把类和类有机的集成。通过其中的方案来设计我们自己的程序。
在WINMAIN.CPP源文件的AfxWinMain()函数中,CWinApp* pApp = AfxGetApp();,AfxGetApp会获得一个指向CWinApp的指针。实际上此处获得的指针就是批注1处的CXXXApp构造函数的指针。实际上就是theApp的指针。pApp存储的是派生类的对象的一个指针。
if (pApp != NULL &&!pApp->InitApplication()),pApp->InitApplication()为MFC内部管理调用的一个函数。
CWinThread* pThread = AfxGetThread();此处同样获取了指向theApp对象的指针。if (!pThread->InitInstance())通过Go to Defination ofInitInstance(),可以查看到InitInstance的定义:virtual BOOLInitInstance()为虚函数,根据多态性的原理pThread所调用的InitInstance()为子类实现的InitInstance()函数。
nReturnCode = pThread->Run();,指向theApp子类的指针调用了Run方法,Run()方法完成消息循环。
CXXXApp作用:在CXXXApp构造函数处设置断点开始调试运行,进入到CXXXApp的构造函数处,再调试运行,会运行到_tWinMain函数入口断点处。在CXXXApp theApp全局变量处设置断点,调试运行:运行到CXXXApp theApp处,再调试运行,运行到CXXXApp的构造函数断点处,再次调试运行,运行到_tWinMain函数入口断点处。出现此种运行顺序的原因:不管是全局对象还是全局变量,都会在程序运行之前(即入口函数main函数加载之前),为其分配好了内存空间。
为什么在main函数调用之前声明全局对象CXXXApp theApp,并让其在main函数之前去执行?全局变量的作用?:theApp是一个应用程序对象,每一个MFC的应用程序中有且只有一个theApp应用程序对象,它表示了应用程序本身。由于CXXXApp类是从CWinApp中派生出来的,在C++中构造派生类之前会调用基类的构造函数对其进行构造,由此,可以通过CXXXApp类的对象theApp与CWinApp类关联起来,并对CWinApp进行调用。从而完成应用程序的一些初始化工作。当基类的构造和子类的构造完成之后,就为其分配了内存空间。
打开APPCORE.CPP源文件,找到CWinApp的构造函数,CWinApp::CWinApp(LPCTSTR lpszAppName)。可以看到构造函数中的参数LPCTSTRlpazAppName,而在CXXXApp的构造函数是无参的。在C++中,如果基类的构造函数带有形参,在子类构造的时候,需要向基类,显式的去调用基类带参数的构造函数。而在MFC中CXXXApp的构造函数为什么不需要显示的去调用基类的构造函数?因为lpszAppName是一个带缺省值为NULL的参数,因此在派生类构造的时候就不需要向其显式的调用基类的带参数构造函数。
在CWinApp的构造函数中,pThreadState->m_pCurrentWinThread = this;this指针指代的是派生类的对象即theApp对象,因为根据继承性的原理,
设计窗口类,在MFC当中预先定义好了几种缺省的窗口类,而我们只需要调用AfxEndDeferRegisterClass()函数在运行的时候去注册就可以了,会根据我们产生的不同程序选择不同的窗口类去注册。在WINCORE.CPP源文件中可以查看到AfxRegisterClass的实现函数
BOOLAFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
WNDCLASSwndcls;
//获取一个类的信息,查看窗口类是否已经注册。如果窗口类已经注册,返回真值。
if(GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName,
&wndcls))
{
//class already registered
returnTRUE;
}
//如果窗口类没有注册,就去注册。
if(!::RegisterClass(lpWndClass))
{
TRACE1("Can'tregister window class named %s\n",
lpWndClass->lpszClassName);
returnFALSE;
}
if(afxContextIsDLL)
{
AfxLockGlobals(CRIT_REGCLASSLIST);
TRY
{
//class registered successfully, add to registered list
AFX_MODULE_STATE*pModuleState = AfxGetModuleState();
LPTSTRlpszUnregisterList = pModuleState->m_szUnregisterList;
//the buffer is of fixed size -- ensure that it does not overflow
ASSERT(lstrlen(lpszUnregisterList)+ 1 +
lstrlen(lpWndClass->lpszClassName)+ 1 <
_countof(pModuleState->m_szUnregisterList));
//append classname + newline to m_szUnregisterList
lstrcat(lpszUnregisterList,lpWndClass->lpszClassName);
TCHARszTemp[2];
szTemp[0]= '\n';
szTemp[1]= '\0';
lstrcat(lpszUnregisterList,szTemp);
}
CATCH_ALL(e)
{
AfxUnlockGlobals(CRIT_REGCLASSLIST);
THROW_LAST();
//Note: DELETE_EXCEPTION not required.
}
END_CATCH_ALL
AfxUnlockGlobals(CRIT_REGCLASSLIST);
}
returnTRUE;
}
打开WINFRM.CPP源文件,查找到PreCreateWindow()的定义函数,当在CMainFrame类的PreCreateWindow()中没有赋值时,即参数为NULL,可以看到在基类CFrameWnd的PreCreateWindow()定义函数中对NULL进行了判断,如果参数为NULL,利用判断语句VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));判断当前的类型窗口类有没有注册,如果没有注册,就注册AFX_WNDFRAMEORVIEW_REG类型。在AFXIMPL.H头文件中可以看到#defineAfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass),可以看出AfxDeferRegisterClass()实际上是一个宏,实际上是WINCORE.CPP源文件中的AfxEndDeferRegisterClass()函数。在函数PreCreateWindow()中可以看到选择的是_afxWndFrameOrView类。
在WINCORE.CPP源文件中,查找到CreateEx()函数,同时可以看到CreateEx()是被CreateEx的重载函数调用的。在逆向查找重载的CreateEx()被谁调用时,可以发现是被WINFRM.CPP源文件中的Create()调用的。在查看重载的CreateEx()函数时,发现它并非虚函数,
m_pMainWnd是一个指针,保存了应用程序的框架的指针,即指向CMainFrame框架的指针,
消息循环的实现:当有消息产生时,将消息放入到消息队列中。在源文件WINMAIN.CPP中AfxWinMain函数入口最后对Run函数的调用,实现了消息的循环。而Run()函数的定义是在THRDCORE.CPP源文件中,
intCWinThread::Run()
{
ASSERT_VALID(this);
//for tracking the idle time state
BOOLbIdle = TRUE;
LONGlIdleCount = 0;
//acquire and dispatch messages until a WM_QUIT message is received.
for(;;)
{
//phase1: check to see if we can do idle work
while(bIdle &&
!::PeekMessage(&m_msgCur,NULL, NULL, NULL, PM_NOREMOVE))
{
//call OnIdle while in bIdle state
if(!OnIdle(lIdleCount++))
bIdle= FALSE; // assume "no idle" state
}
//phase2: pump messages while available
do
{
//pump message, but quit on WM_QUIT
//构成了一个消息循环
if(!PumpMessage())
returnExitInstance();
//reset "no idle" state after pumping "normal" message
if(IsIdleMessage(&m_msgCur))
{
bIdle= TRUE;
lIdleCount= 0;
}
}while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE);// not reachable
}
13、(1)首先利用全局对象theApp来启动应用程序(2)因为theApp是全局对象,会调用它自身的构造函数,因为它是CWinApp的子类,所以它会首先调用父类的构造函数。于是,CWinApp的构造函数就会被调用,在CWinApp的构造过程中,完成了程序的一些初始化工作,同时将子类的指针保存起来,保存之后进入winmain函数。(3)在_tWinMain()中调用了AfxWinMain()函数。(4)在AfxWinMain()函数中,通过:CWinThread* pThread =AfxGetThread();和CWinApp* pApp= AfxGetApp();可以获取到子类的指针。利用子类的指针去调用一个虚函数InitInstance(),根据多态性原理,就会实现调用子类里的InitInstance()函数,进入到子类的InitInstance()。(5)在InitInstance函数中完成了程序的一些初始化工作,包括:窗口类的注册(AfxEndDeferRegisterClass)、窗口类的创建(PreCreateWindow、CreateEx、Create)、窗口的显示、窗口的更新等等,然后进入消息循环。(6)消息循环不断的在其中运行,将消息通过消息响应函数进行处理。
14、CXXXView类窗口覆盖在CMainFrame类窗口之上的。
15、CXXXDoc类用来存储、加载数据。而CXXXView类用来显示、修改数据。
16、CMainFrame、CXXXDoc、CXXXView类是如何组织在一起的?在CXXXApp类中的InitInstance函数中,CSingleDocTemplate* pDocTemplate; pDocTemplate指针。
pDocTemplate= new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame),// main SDI frame window
RUNTIME_CLASS(CTestView));它们通过一个单文档的模板组合在一起。
AddDocTemplate(pDocTemplate); //利用AddDocTemplate增加到文档模板中。
17、窗口类的对象和窗口的关系:窗口类的对象和窗口的纽带是窗口类的对象内部的一个成员变量是句柄,该成员变量保存了相关的窗口的句柄。当窗口类的对象销毁时,窗口跟着销毁。因为窗口类的对象销毁,它与窗口之间的纽带也跟着销毁。但是,当窗口关闭时,除非窗口类的对象的生命周期结束,否则,窗口类的对象还是存在的,窗口类的对象还是可以用的。所以窗口类的对象和窗口并不是等同的。
18、Button控件的产生是在CMainFrame框架产生之前还是产生之后呢?在框架窗口产生之后再产生Button控件。CMainFrame::OnCreate()函数用来响应窗口创建消息的函数。窗口创建的时候都会产生WM_WindowMESSAGE消息。
批注2
批注1