《VC++深入详解》学习笔记[2]——第3章MFC框架程序剖析

 

第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.窗口类、窗口类对象、窗口

窗口类对象与窗口并不是一回事,它们之间唯一的关系是窗口类内部定义了一个窗口句柄变量,保存了与这个窗口类对象相关的那个窗口的句柄。窗口销毁时与之对应的窗口类对象销毁与否要看其生命周期是否结束,但窗口类对象销毁时,与之相关的窗口也将销毁。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、在视频Lesson2中,在介绍构造函数时,我说:“构造函数最重要的作用是创建对象本身,对象内存的分配由构造函数来完成的”,这句话是错的,对象内存的分配和构造函数没有关系,对象内存的分配是由编译器来完成的,构造函数的作用是对对象本身做初始化工作,也就是给用户提供初始化类中成员变量的一种方式,在类对象有虚表的情况下,构造函数还对虚表进行初始化。 另外,我说:“C++又规定,如果一个类没有提供任何的构造函数,则C++提供一个默认的构造函数(由C++编译器提供)”,这句话也是错误的,正确的是: 如果一个类中没有定义任何的构造函数,那么编译器只有在以下三种情况,才会提供默认的构造函数: 1、如果类有虚拟成员函数或者虚拟继承父类(即有虚拟基类)时; 2、如果类的基类有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数); 3、在类中的所有非静态的对象数据成员,它们对应的类中有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数)。 二、在视频 Lesson4 的Code 中,画扇形用如下代码即可: if(m_bDraw == TRUE) { dc.MoveTo(m_ptOrigin); dc.LineTo(point); } 带边线的扇形用如下代码即可: if(m_bDraw == TRUE) { dc.MoveTo(m_ptOrigin); dc.LineTo(point); dc.LineTo(m_ptOld); m_ptOld = point; } 三、在视频Lesson8中,关于在对话框上放置组合框的问题,我说“如果拖动的矩形较小,组合框的列表框部分将无法显示,此时也无法调整组合框的上下位置的大小了”。实际上,组合框的上下位置还是可以调整的,调整的办法如下: 在对话框资源处于编辑状态时,将鼠标移动到组合框控件右边向下的箭头上,当鼠标变成上下箭头形状时,单击鼠标左键,此时可以看到举行框围绕着组合框。将鼠标移动到该矩形框下端的蓝色小方块上,当鼠标变成上下箭头形状时,按住鼠标左键向下拖动,直到把组合框的下拉列表框范围拖动到合适的大小时松开鼠标左键。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值