本章主要是从MFC程序代码中,找出一个windows程序原本该有的程序入口点、窗口类注册、窗口产生、消息循环、窗口函数等操作。抽丝剥茧彻底理解一个MFC程序的诞生与结束。
MFC程序需要windowsCruntime函数库、DLLimport函数库以及MFC函数库。
WindowsCruntime函数库为:
LIBC.LIB静态链接版本
MSVCRT.LIB动态链接版本
MSVCRTD.LIBDebug版本。
DLLimport函数库
GDI32.LIB
USER32.LIB
KERNEL32.LIB
MFC函数库
MFC42.LIB
MFC42D.LIB
MFCS42.LIB
MFCS42D.LIB
......
SDK程序需要载入windows.h而MFC程序需要另外一些.h文件:
1:STDAFX.H它用来载入其他MFC头文件。
2:AFXWIN.H它和它载入的文件声明了所有的MFC类。其内包含AFX.H,后者包含了AFXVER_.H,后者又载入了AFXV_W32.H,后者又载入WINDOWS.H。
3:AFXEXT.H使用工具栏、状态栏的程序必须载入这个文件
4:AFXDLGS.H凡使用通用对话框(Commondialog)的MFC程序需要载入此文件。它内部包含COMMDLG.H
5:FXCMN.H凡使用windows9x新增通用行控件(commoncontrol)的MFC程序需要载入此文件。
6:AFXCOLL.H凡使用MFC提供的容器都需要载入此文件。
7:AFXDLLX.H凡使用MFCextensionDLLs需要载入此文件。
8:AFXRES.HMFC程序的RC文件必须载入此文件。此文件中对于标准资源的ID都有默认值。它们定义于此文件中。
MFC头文件均置于MFC\INCLUDE中。
MFC把有着相当固定行为的WinMain内部操作,封装在CWinApp中。把有着相当固定行为的WndProc内部操作封装在CFrameWnd中。也就是说CWinApp代表程序本体。CFrameWnd代表主框窗口。程序员必须以这两个类为基础,派生自己的类,并改写一部分成员函数,以产生适合自己的程序。
说CWinApp取代WinMain,并不是说MFC没有WinMain而是CWinApp完成了由传统WinMain完成的工作。主要工作由CWinApp的三个成员函数完成:
1:virtualboolInitApplication();
2:virtualboolInitInstance();
3:virtualintRun();
此外CWinApp中还有一个从CWinThread继承而来的m_pMainWnd,用以记录程序的主窗口。
以下为CWinApp中有的成员变量:
m_hInstance当前实例句柄
m_hPrevInstance前一实例句柄。
m_lpCmdLine命令行参数
m_CmdShow显示类型
它们是SDK程序中传给WinMain的四个参数。
m_pMainWnd主窗口
m_pActiveWnd当前激活窗口。
m_pszAppName程序名
以上几个可以在InitInstance中修改。
m_pszExeName可执行程序名称。
m_pszHelpFilePath
m_pszProfileName
CFrameWnd主要用来掌握一个窗口,它取代了WndProc的地位。
我们派生的CMyFrameWnd类应该定义消息响应函数。如
afx_msgvoidOnPaint();
afx_msgvoidOnAbout();
使用DELCARE_MESSAGE_MAP将消息和消息处理函数关联。这在前面章节已经介绍过。
当程序执行时,全局的theApp对象产生,于是调用构造函数,并初始化各成员变量。一系列的操作使整个类继承体系被调动起来。
theApp在WinMain之前被构造,此后WinMain被调用。WinMain早已被写好,被链接器接入到程序中。_tWinMain中的_t是为了支持Unicode而支持的宏。它实际上就是WinMain,就像console程序里_tmain跟main的关系。
extern"C"intWINAPI_tWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPTSTRlpCmdLine,intnCmdShow)
{
returnAfxWinMain(hInstance,hPrevInstance,lpCmdLine,
nCmdShow);
}
AfxWinMain函数位于MFC的WINMAIN.CPP文件中。稍加整理发现它主要做了以下事:
IntAFXAPIAfxWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPTSTRlpCmdLine,intnCmdLine)
{
IntnReturnCode=-1;
CWinApp*pApp=AfxGetApp();
AfxWinInit(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
pApp->InitApplication()
pApp->InitInstance();
nReturnCode=pApp->Run();
}
AfxGetApp是一个全局函数,它定义于AFXWIN1.INL
_AFXWIN_INLINECWinApp*AFXAPIAfxGetApp()
{
returnafxCurrentWinApp;
}
而afxCurrentApp是一个宏,定义域AFXWIN.H中。
#defineafxCurrentWinApp\
AfxGetModuleState()->m_pCurrentWinApp
AfxWinInit是继CWinApp构造函数之后的第一个操作,它进行一些对theApp对象的初始化操作,如将传给WinMain的四个参数赋值给theApp的对应成员、初始化线程等等。
CMyWinApp继承自CWinApp,没有改写虚函数InitApplication,因此调用的是CWinApp的InitApplication。在前面的关键技术仿真时,我们在Initapplication里注册了窗口类,但是MFC并不是这样做的。CMyWinApp重写了InitInstance函数,因此pApp->InitInstance调用的是CMyWinApp的版本。在此函数内,MFC构造了CMyFrameWnd,在CMyFrameWnd的构造函数内调用了Create函数,它产生一个窗口,它需要八个参数,其中六个都有了默认值。只有前两个需要指定。
第一个参数lpszClassName,用以指定WNDCLASS类,使用NULL表示使用MFC内建的窗口类产生一个标准的窗口,但是此时我们并没与发现窗口注册的操作。因为在Create函数内会调用注册窗口类的函数。这稍候会做介绍。
第二个参数:lpszWindowName,指定窗口标题。很简单不介绍。
第三个参数指定窗口风格。
第四个参数指定窗口的位置与大小。默认值rectDefault是CFrameWnd的一个static成员变量。告诉windows以默认方式指定窗口位置和大小。同时也可以手动指定,如CRect(40,20,240,460);
第五个参数用以指定父窗口。对于一个顶层窗口来说它应该为NULL,表示它没有父窗口。但是其实它是有的,它的父窗口是desktop窗口。
第六个参数指定菜单,它是在RC文件中定义的。
第八个参数pContext指向CCreateContext结构指针,MFC利用它在文档视图结构初始化外框窗口。不具备文档视图结构的程序此值为NULL.
CFrameWnd::Create函数在调用时,会引发窗口类的注册操作,这是通过CreateEx来实现的。由于CFrameWnd没有重写CreateEx。所有调用的CreateEx实际调用的是其父类CWnd的CreateEx函数。
CreateEx会进行窗口的注册,但这是通过PreCreateWindow进行的,CWnd和CFrameWnd都定义了PreCreateWindow函数,但由于this指针指向的类型的缘故,此处调用的是CFrameWnd的PreCreateWindow。
看PreCreateWindow定义:
boolCFrameWnd::PreCreareWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
cs.lpszClass=_afxWndFrameOrView;
}
//................
}
AfxDeferRegisterClass是一个定义于AFXIMPL.H的宏。
#defineAfxDeferRegisterCalss(fClass)\
((afxRegisteredClasses&fClass)?true\
:AfxEndDeferRegisterClass(fClass);
这个宏表示如果afxRegisterClasses的值显示系统已经注册了fClass,窗口类就啥也不做。否则就调用AfxEndDeferRegisterClass注册该窗口类。
afxRegisterClasses是一个旗标变量,用来记录已经注册了哪些窗口类。
#defineafxRegisterClassesAfxGetModuleState->
m_fRegisteredClasses();
不同类的PreCreateWindow成员函数都是在窗口产生之前一刻被调用,准备来注册窗口类。如果指定的窗口类为NULL,那么就是用系统默认类。MFC定义了五个不同的默认类。从CWnd及其各个派生类的成员函数PreCreateWindow可以看出,针对不同的窗口使用哪些窗口类。
boolCWnd::PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WND_REG);
//...................
cs.lpszClass=_afxWnd;//CWnd类使用默认的_afxWnd类。
}
}
boolCFrameWnd::PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WND_REG);
//...................
cs.lpszClass=_afxWndFrameOrView;//CFrameWnd类使用默认的_afxWndFrameOrView类。
}
}
boolCMDIFrameWnd::PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WND_REG);
//...................
cs.lpszClass=_afxWndMDIFrame;//CMDIFrameWnd类使用默认的_afxWndFrameOrView类。
}
}
boolCMDIChildWnd::PreCreateWindow(CREATESTRUCT&cs)
{
///,...............................
ReturnCFrameWnd::PreCreateWnd(cs);//表示此类使用的窗口类是_afxWndFrameOrView;
}
boolCView::PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WND_REG);
//...................
cs.lpszClass=_afxWndFrameOrView;//CView类使用默认的_afxWndFrameOrView类。
}
}
这五种默认的类对应五种窗口。
CMyFrameWnd::CMyFrameWnd结束后,窗口已经产生。调用ShowWindow令窗口显示出来,调用UpdateWindow函数发送WM_PAINT消息使窗口重绘。
pApp->Run();相当于CMyWinApp::Run,由于CMyWinApp继承自CWinApp但并未改写Run,因此相当于调用CWinApp::Run();它被定义在APPCORE.CPP中。
intCWinApp::Run()j
{
if(m_pMainWnd==NULL&&AfxOleGetUserCtrl)
{
AfxPostQUitMessage(0);
}
returnCWinThread::Run();
}
IntCWinThread::Run()
{
BoolbIdle=true;
LongiIdleCoun=0;
While(1)
{
While(idle&&!::PeekMessage(&m_msgCur,NULL,
NULL,NULL,PM_NOREMOVE))
{
If(!OnIdle(lIdleCount++))
bIdle=true;
}
}
Do
{
If(!PumpMessage())
ReturnExitInstance();
If(IsIdleMessage(&m_msgCur))
{
bIdle=0;
lIdleCount=0;
}
}while(::PeekMessage(&m_msgCur,NULL,NULL,
NULL,PM_NOREMOVE);
}
在CWinThread::Run中,消息循环调用了PeekMessage,看消息队列是否有消息,如没有同时当前为空闲状态亲爱,就调用OnIdle函数。
BoolCWinThread::PumpMessage()
{
If(!::GetMessage(&m_msgCur,NULL,NULL,NULL)
{
Returnfalse;
}
If(m_msg.message!=WM_KICKIDLE&&
!PreTranslateMessage(&m_msgCur)
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
}
窗口函数实际上也是由MFC提供的,如DefWndProc。WinMain由MFC提供,窗口类也由MFC注册完成,连窗口函数都由MFC提供,这大大减轻了程序员的工作量。
MFC把消息分为三大类:
1:标准Windows消息。WM_xx。消息与消息响应函数的对应规则为,如
ON_WM_CHAR(WM_CHAR,OnChar);
ON_WM_CLOSE(WM_CLOSE,OnClose);
ON_WM_DESTROY(WM_DESTROY,OnDestroy);
2:命令消息,WM_COMMAND。消息与消息响应函数的对应规则为:
ON_COMMAND(IDM_ABOUT,OnAbout);
ON_COMMAND(ID_FILEOPEN,OnFileOpen);
3:通知消息。有控件产生.
Button控件ON_BN_CLICKED(id,memberFunc);
Edit控件ON_EN_SETFOCUS(id,memberFunc);
各个消息函数都是以afx_msgvoid类型的。
如果某个消息在消息映射表中找不到对应项,则它会向基类流动,这被称为消息传递(MessageRouting)。如果流动到最基础的类,仍然无法找到对应项,则会有默认的函数来处理。就像SDK程序的DefWindowProc一样。从CCmdTarget派生的类都可以设定自己的MessageMap。
程序的诞生过程:
1:CMyWinApp对象产生,初始化各成员变量。
2:AfxWinMainzhxingAfxWinInit,后者又调用AfxInitThread,把消息队列加大到96.
3:AfxWinMain调用InitInstance,我们必须改写它。
4:CMyWinApp::InitInstance内new了一个CMyFrameWnd对象。
5:在CMyFrameWnd构造函数内调用Create产生主窗口,为Create的窗口类传入NULL,于是根据MFC窗口种类自动注册窗口。
6:调用ShowWindow显示窗口。
7:调用UpdateWindow发出WM_PAINT。
8:执行Run,进入消息循环。
9:程序获得WM_PAINT消息,。
10:WM_PAINT经由::DisPatchMessage送到CWnd::DefWindowProc中。
11:在消息映射表查找此消息,并调用响应消息处理函数。
标准消息的处理程序有标准的命名,如WM_PAINT由OnPaint处理。
程序结束过程:
1:单击Close,发出WM_CLOSE。
2:CMyFrameWnd没有设置WM_CLOSE处理函数,因此交由默认处理函数处理。
3:默认处理函数调用::DestroyWindow发出WM_QUIT.
4:CWinApp::Run收到WM_QUTI后会结束其内部的消息循环,然后调用ExitInstance。
5:回到WinMain函数,执行AfxWinTerm结束程序。
Callback是回调函数,它是被windows系统调用的。因为某些windowsAPI函数要求以callback函数作为其参数之一,如SetTimer,此函数会在条件满足时调用该callback函数。当类的成员作为windows的callback函数时,该成员函数必须是static类型。线程入口函数也一样