学习笔记之深入浅出MFC 第9章 仿真MFC 之一

在文章开始是我们的观点就强调过了,要想用好一个工具,就必须深入了解这个工具的工作原理。而仿真,正是最好的方法。

如何仿真呢?我们在console程序中仿真MFC,这样可以把程序结构的负荷降到最低。作者在仿真中的原则是:简化再简化,简化到不能再简化。请注意,以下所有程序的类层次结构、类名称、变量名称、结构名称、函数名称、函数内容,都以MFC为仿真对象,具体而微。

在档案的安排上,作者把仿真MFC的类集中在MFC.H和MFC.CPP中,把自己派生的类集中在MY.H和MY.CPP中。对于自定义的类,我的命名方式是在父类的名称前面加一个“My”,例如派生自CWinApp者,名为CMyWinApp,派生自CDocument者,名为CMyDoc。

特别说明:我一直认为,扎实的基础是以后进阶高度快速提升的不二法门,所以希望大家能够跟着我沉住气,一点一点挖出MFC整个的运行机制,相信这会对大家和对我自己更有裨益。从这一章开始,我会分析的很细,可能每一节讲的很少,但务必求精不贪多,希望大家可以理解。

1、MFC的类层次结构

首先以一个极简单的程序Frame1,把MFC数个最重要的类的层次关系仿真出来:

这个例程仿真MFC的类层次。后续会在这个类层次上开发新的能力。在这些名为Frame?的范例中,我以MFC程序代码为蓝本,尽量仿真MFC的内部行为,并且使用完全相同的类名称、函数名称、变量名称。这会对我们在后面深入探讨MFC时有莫大的助益。

Frame 1 范例程序

首先是MFC.H头文件:

#include  <iostream>

using namespace std;

class  CObject      //基类CObject

{

public:

CObject::CObject()  { cout << "CObject  Constructor  \n";  }

CObject::~CObject()  {cout << "CObject  Destructor  \n";  };

};

class  CCmdTarget  :  public  CObject

{

public:

CCmdTarget::CCmdTarget()  { cout << "CCmdTarget  Constructor  \n";  }

CCmdTarget::~CCmdTarget()  {cout << "CCmdTarget  Destructor  \n";  };

};

class  CWinThread  :  public  CCmdTarget

{

public:

CWinThread::CWinThread()  { cout << "CWinThread  Constructor  \n";  }

CWinThread::~CWinThread()  {cout << "CWinThread  Destructor  \n";  };

};

class  CWinApp  :  public  CWinThread

{

public:

CWinApp* m_pCurrentWinApp;

public:

CWinApp::CWinApp()   { m_pCurrentWinApp = this;

cout << "CWinApp  Constructor \n"; }   //这个类的对象在创建时就会调用构造函数,所以也就把当前类对象指针保存到了m_pCurrentWinAPP

CWinApp::~CWinApp()  { cout << "CWinApp  Destructor  \n"; }

};

class  CWnd  :  public  CCmdTarget

{

public:

CWnd::CWnd()  { cout << "CWnd  Constructor  \n";  }

CWnd::~CWnd()  { cout << "CWnd  Destructor  \n";  }

};

class  CFrameWnd  :  public  CWnd

{

public:

CFrameWnd::CFrameWnd()  { cout << "CFrameWnd  Constructor  \n";  }

CFrameWnd::~CFrameWnd()  { cout << "CFrameWnd  Destructor  \n";  }

};

class  CView  :  public  CWnd

{

public:

CView::CView()  { cout << "CView  Constructor  \n";  }

CView::~CView()  { cout << "CView  Destructor  \n";  }

};

//全局函数

CWinApp*  AfxGetApp();

然后是MFC.CPP:

#include "MFC.h"

#include "MY.h"

extern  CMyWinApp  theApp;

CWinApp*  AfxGetApp()

{

return  theApp.m_pCurrentWinApp;

}

接下来是从MFC类中派生出来的我们自己的App类:

MY.H头文件:

#include <iostream>

#include "MFC.h"

class CMyWinApp  :  public  CWinApp

{

public:

CMyWinApp::CMyWinApp()   {  cout << "CMyWinApp  Constructor  \n"; }

CMyWinApp::~CMyWinApp()   {  cout << "CMyWinApp  Destructor  \n"; }

};

class  CMyFrameWnd  :  public  CFrameWnd

{

public:

CMyFrameWnd()   {  cout << "CMyFrameWnd  Constructor  \n"; }

~CMyFrameWnd()   {  cout << "CMyFrameWnd  Destructor  \n"; }

};

MY.CPP源文件:

#include  "MY.h"

CMyWinApp  theApp;

void  main()

{

CWinApp*  pApp  =  AfxGetApp();

}

执行的结果:

CObject  Constructor

CCmdTarget  Constructor

CWinThread  Constructor

CWinApp  Constructor

CMyWinApp  Constructor

CMyWinApp  Destructor

CWinApp  Destructor

CWinThread  Destructor

CCmdTarget  Destructor

CObject Destructor

从程序中可以看到,Frame1并没有new任何对象,反倒是有一个全局对象theApp存在。C++规定,全局对象的建构将比程序进入点(在DOS环境为main,在Windows环境为WinMain)更早。所以theApp的构造函数将更早于main。换句话说,你所看到的执行结果中的那些构造函数输出操作全都是在main函数之前完成的。

在解释一下,因为定义theApp这个全局变量的类为CMyWinApp,而这个类是从一系列基类继承下来的,也就意味着你在声明这个全局变量的时候,就已经依次调用了各个类的构造函数和析构函数,所以才会有上面的输出结果。

main函数调用全局函数AfxGetApp以取得theApp的对象指针。这完全是仿真MFC程序的手法。

2、MFC程序的初始化过程

MFC程序也是一个Window程序,它的内部一定也像第1章所述的一样,有窗口注册操作,有窗口产生操作,有消息循环操作,也有窗口函数。这里我们只交代一个程序流程,这个流程正是任何MFC程序的初始化过程的简化。

以下是Frame2范例程序的类层次及其成员。对于那些“除了构造函数与解构函数之外没有其他成员”的类,我们就不在图中展开它们了。

就如曾在第1章解释过的,InitApplication和InitInstance现在成了MFC的CWinApp的两个虚拟函数。前者负责“每个程序只做一次”的操作,后者负责“每一个例程都得做一次”的操作。

通常,系统会为你注册一些标准的窗口类(当然也就准备好了一些标准的窗口函数),应用程序设计者应该在你的CMyWinApp中改写InitInstance,并在其中把窗口产生出来-----这样你才有机会在标准的窗口类中指定自己的窗口标题和菜单。下面是我们新的main函数:

//MY.CPP

CMyWinApp theApp;

void main()

{

CWinApp* pApp = AfxGetApp();

pApp->InitApplication();

pApp->InitInstance();

pApp->Run();

}

其中,pApp指向theApp全局对象。这里我们开始看到了虚拟函数的妙用:

pApp->InitApplication()调用的是CWinApp::InitApplication,

pApp->InitInstance() 调用的是CMyWinApp::InitInstance( 因为CMyWinApp改写了它),

pApp->Run()调用的是CWinApp::Run,

好,请注意一下CMyWinApp::InitInstance的操作,以及它所引发的行为:

BOOL CMyWinApp::InitInstance()

{

cout << "CMyWinApp::InitInstance \n";

m_pMainWnd = new CMyFrameWnd; //还记得吗?动态创建时引发CMyFrameWnd::CMyFrameWnd构造函数

return TRUE;

}

CMyFrameWnd::CMyFrameWnd()

{

Create(); //Create是虚拟函数,但是CMyFrameWnd未改写它,所以引发父类的CFrameWnd::Create

}

BOOL CFrameWnd::Create()

{

cout << " CFrameWnd::Create \n";

CreateEx(); //CreateEx是虚拟函数,但CFrameWnd未改写之,所以引发CWnd::CreateEx

return TRUE;

}

BOOL CWnd :: CreateEx()

{

cout << " CWnd:: CreateEx \n";

PreCreateWindow(); //这是一个虚拟函数,CWnd中有定义,CFrameWnd也改写了它。那么你说这里到底是调用CWnd::PreCreateWindow还是CFrameWnd::PreCreateWindow呢?

return TRUE;

}

BOOL CFrameWnd :: PreCreateWindow()

{

cout<<"CFrameWnd::PreCreateWindow \n";

return TRUE;

}

其实,这里调用的是CFrameWnd::PreCreateWindow。这便是第2章的“Object slicing与虚拟函数”一节所说的“虚拟函数的一个极重要的行为方式”。

当然,在这里这些函数什么动作也没做,只是输出一个标识字符串,我们现在主要的目的是让你先熟悉MFC程序的流程。

执行结果:

CWinApp::InitApplication

CMyWinApp::InitInstance

CMyFrameWnd::CMyFrameWnd

CFrameWnd::Create

CWnd::CreateEx

CFrameWnd::PreCreateWindow

CWinApp::Run

CWinThread::Run

Frame2范例程序

MFC.H头文件

#define  BOOL  int

#define  TRUE  1

#define  FALSE  0

#include  <iostream>

using namespace std;

class  CObject      //基类CObject

{

public:

CObject::CObject()  { }

CObject::~CObject()  { }

};

class  CCmdTarget  :  public  CObject

{

public:

CCmdTarget::CCmdTarget()  {  }

CCmdTarget::~CCmdTarget()  {  }

};

class  CWinThread  :  public  CCmdTarget

{

public:

CWinThread::CWinThread()  {  }

CWinThread::~CWinThread()  {  }

virtual  BOOL  InitInstance()

{

cout <<"CWinThread :: InitInstance \n";

return  TRUE;

}

virtual  BOOL  Run()

{

cout <<"CWinThread :: Run \n";

return  TRUE;

}

};

class  CWinApp  :  public  CWinThread

{

public:

CWinApp* m_pCurrentWinApp;

CWnd*  m_pMainWnd;

public:

CWinApp::CWinApp()   { m_pCurrentWinApp = this;}

CWinApp::~CWinApp()  {  }

virtual  BOOL  InitApplication()

{

cout <<"CWinApp :: InitApplication \n";

return  TRUE;

}

virtual  BOOL  InitInstance()

{

cout <<"CWinApp :: InitInstance \n";

return  TRUE;

}

virtual  BOOL  Run()

{

cout <<"CWinApp :: Run \n";

return  CWinThread::Run();

}

};

class  CWnd  :  public  CCmdTarget

{

public:

CWnd::CWnd()  {  }

CWnd::~CWnd()  {  }

virtual  BOOL  Create();

BOOL  CreateEx();

virtual  BOOL  PreCreateWindow();

};

class  CFrameWnd  :  public  CWnd

{

public:

CFrameWnd::CFrameWnd()  {   }

CFrameWnd::~CFrameWnd()  {   }

BOOL  Create();

virtual  BOOL PreCreateWindow();

};

class  CView  :  public  CWnd

{

public:

CView::CView()  {  }

CView::~CView()  {   }

};

//全局函数

CWinApp*  AfxGetApp();

MFC.CPP源文件:

#include "MFC.h"

#include "MY.h"

extern  CMyWinApp  theApp;

CWinApp*  AfxGetApp()

{

return  theApp.m_pCurrentWinApp;

}

 

BOOL  CWnd::Create()

{

cout << "CWnd ::Create \n";

return TRUE;

}

BOOL  CWnd::CreateEx()

{

cout<<"CWnd::CreateEx  \n";

PreCreateWindow();

return  TRUE;

}

BOOL  CWnd::PreCreateWindow()

{

cout << "CWnd ::PreCreateWindow \n";

return TRUE;

}

BOOL  CFrameWnd::Create()

{

cout<<"CFrameWnd::Create \n";

CreateEx();

return  TRUE;

}

BOOL  CFrameWnd::PreCreateWindow()

{

cout<<"CFrameWnd::PreCreateWindow  \n";

return  TRUE;

}

MY.H头文件

#include <iostream>

#include "MFC.h"

class CMyWinApp  :  public  CWinApp

{

public:

CMyWinApp::CMyWinApp()   {   }

CMyWinApp::~CMyWinApp()   {   }

virtual  BOOL  InitInstance();

};

class  CMyFrameWnd  :  public  CFrameWnd

{

public:

CMyFrameWnd();

~CMyFrameWnd()   {  cout << "CMyFrameWnd  Destructor  \n"; }

};

MY.CPP源文件:

#include  "MY.h"

CMyWinApp  theApp;

BOOL CMyWinApp::InitInstance()

{

cout<<"CMyWinApp::InitInstance  \n";

m_pMainWnd  =  new  CMyFrameWnd;

return  TRUE;

}

CMyFrameWnd::CMyFrameWnd()

{

cout<<"CMyFrameWnd::CMyFrameWnd \n";

Create();

}

void  main()

{

CWinApp*  pApp  =  AfxGetApp();

pApp->InitApplication();

pApp->InitInstance();

pApp->Run();

}

程序过程有些繁琐,所以我绘制了一下流程图,从总体上来看整个过程,应该更有注意理解:

下面是在InitInstance()中创建窗口的过程:

时间: 2024-12-15 21:09:23

学习笔记之深入浅出MFC 第9章 仿真MFC 之一的相关文章

学习笔记之深入浅出MFC 第9章 仿真MFC之二

RTTI(执行期类型识别) 在前面章节中我们介绍过Visual C++4.0支持RTTI,重点不外乎是: 1.编译时需选用/GR(/GR的意思是enable C++ RTTI) 2.包含typeinfo.h 3.使用新的typeid运算符. 其实,MFC在编译器支持RTTI之前,就有了这项能力.我们现在要以相同的手法,在Console程序中仿真出来.我希望我的类库具备IsKindOf的能力,能在执行期侦测某个对象是否"属于某种类",并传回TRUE或FALSE.以前一章的Shape为例,

《python基础教程(第二版)》学习笔记 基础部分(第1章)

<python基础教程(第二版)>学习笔记基础部分(第1章)IDEWindows: IDLE(gui), Eclipse+PyDev; Python(command line);Linux/Unix: python >>> 1/2=0 注意整除得0>>> from __future__ import division 执行普通的除法python -Qnew 执行普通的除法 //整除,  1//2=0:%取余数:**乘幂长整型数: 末尾带L十六进制,以0x开头

学习笔记之深入浅出MFC 第8章 C++重要性质----虚拟函数与多态(Polymorphism)

1.虚拟函数的由来 上面我们曾经提过一个例子: CShape shapes[5]; . . . //令5个shapes各为矩形.正方形.椭圆形.圆形.三角形 for ( int i = 0;  i<5;  i++) { shapes[i].display(); } 在上一节中我们说这种一般化的操作无法完成.你还记得为什么吗?是这样的,上面一节中讲到,由于每一个子类图形的绘制不同,所以display()各不相同,所以无法提升到基类中去.那么用基类定义的shapes[]数组,当然也就没有displa

课本学习笔记1:第一、二章 20135115臧文君

第一章 Linux内核简介 注:作者:臧文君,原创作品转载请注明出处. 一.Unix的历史 1.1969年,Dennis Ritchie和Ken Thompson,Unix. 2.Unix产生于贝尔试验室的一个失败的多用户操作系统Multics. 第一个被广泛使用的Unix版本是第6版,称为V6. 3.进一步开发: 加州大学伯克利分校:BSD(Berkeley Software Distributions). 4.Unix系统强大的根本原因:策略和机制分离的设计理念,确保了Unix系统具备清晰的

JavaScript高级程序设计(第三版)学习笔记20、21、23章

第20章,JSON JSON(JavaScript Object Notation,JavaScript对象表示法),是JavaScript的一个严格的子集. JSON可表示一下三种类型值: 简单值:字符串,数值,布尔值,null,不支持js特殊值:undefined 对象:一组无序的键值对 数组:一组有序的值的列表 不支持变量,函数或对象实例 注:JSON的字符串必须使用双引号,这是与JavaScript字符串最大的区别 对象 { "name":"Nicholas"

JavaScript高级程序设计(第三版)学习笔记22、24、25章

第22章,高级技巧 高级函数 安全的类型检测 typeof会出现无法预知的行为 instanceof在多个全局作用域中并不能正确工作 调用Object原生的toString方法,会返回[Object NativeConstructorName]格式字符串.每个类内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名. 原生数组的构造函数名与全局作用域无关,因此使用toString方法能保证返回一致的值,为此可以创建如下函数: function isArray(value)

JavaScript高级程序设计(第三版)学习笔记11、12、17章

第11章, DOM扩展 选择符 API Selector API Level1核心方法querySelector .querySelectorAll,兼容的浏览器可以使用 Document,Element 实例调用它们,支持浏览器:IE8+,Firefox3.5+,Safari3.1+,chrome,Opera10+ querySelector方法 接收一个 CSS选择符,返回与该模式匹配的第一个元素 通过 Document类型调用该函数,会在文档范围查找匹配元素,通过 Element类型调用该

JavaScript高级程序设计(第三版)学习笔记8、9、10章

第8章,BOM BOM的核心对象是window,具有双重角色,既是js访问浏览器的一个接口,又是ECMAScript规定的Global对象.因此,在全局作用域中声明的函数.变量都会变成window对象的属性和方法. 例: var age = 20; function sayAge(){ alert(this.age); } alert(window.age); //20 window.sayAge(); //20 定义全局变量与在window对象上直接定义属性区别:全局变量不能通过delete操

JavaScript DOM编程艺术-学习笔记(第八章、第九章)

第八章 1.小知识点: ①某些浏览器要根据DOCTYPE 来决定页面的呈现模式(标准模式 / 怪异模式--也称兼容模式): 兼容模式意味着浏览器要模仿老一辈的浏览器的怪异行为,来让老站点得到运行,并让不规范的页面得到运行. 使用时应避免出发兼容模式. html5 DOCTYPE默认的是标准模式 ②abbr标签--简称.缩写.此标签到ie7才被ie支持 2.js只能充实文档内容,避免使用dom技术来创建核心内容.    避免使用dom设置重要样式 第九章 1.css正在利用伪类(例如:hover.