三、MFC 六大关键技术之仿真 (学习笔记)

演化(evolution)永远在进行,
这个世界却不是每天都有革命(revolution)发生。
Application Framework 在软件界确实称得上具有革命精神。

项目目录:
mfc31:全局对象theApp的构造过程
mfc32:使用cWinApp通过afxgetapp()指向theApp并调用initapplication、initinstance的过程(有图有真相)
mfc33:RTTI宏的初步应用
mfc34:上述例子的扩充,iskindof函数的实现

mfc37:MessageMap的搭建
mfc38:BT MessageMap Route(Requires a strong heart!)

仿真M F C?有必要吗?意义何在?如何仿真?

我已经在序言以及导读开宗明义说过了,这本书除了教导你使用MFC,另一个重要的功能是让你认识一个application framework  的内部运作。以M F C  为教学载具,我既可以让你领略application framework的设计方式,更可以让你熟悉MFC  类别,将来运用时得心应手。呵,双效合一。

整个MFC4. 0多达189个类别,源代码达252个实作档,58个头文件,共10MB  之多。MFC4. 2又多加了29 个类别。这么庞大的对象,当然不是每一个类别每一个数据结构都是我的仿真目标。我只挑选最神秘又最重要,与应用程序主干息息相关的题目,包括:

■ M F C  程序的初始化过程
■ RTTI(Runtime Type Information)执行时期型别信息
■ Dynamic Creation动态生成
■ Persistence  永续留存
■ Message Mapping 消息映射
■ Message Rout ing 消息绕行

MFC 本身的设计在Application Framework之中不见得最好,敌视者甚至认为它是个Minotaur(注)!但无论如何,这是当今软件霸主微软公司的产品,从探究application framework  设计的角度来说,实为一个重要参考;而如果从选择一application framework作为软件开发工具的角度来说,单就就业市场的需求,我对MFC的推荐再加10 分!

注:Minotaur 是希腊神话中的牛头人身怪物,居住在迷宫之中。进入迷宫的人如果走不出来,就会被一口吃掉!

另一个问题是,为什么要仿真?第三篇第四篇各章节不是还要挖MFC源代码来看吗?原因是MFC太过庞大,我必须撇开枝节,把唯一重点突显出来,才容易收到教育效果。而且,仿真才能实证嘛!

如何仿真?我采用文字模式,也就是所谓的Console 程序,这样可以把程序结构的负荷降到最低。但是像消息映射和消息绕行怎么办?消息的流动是Windows程序才有的特征啊!唔,看了你就知道。

我的最高原则是:简化再简化,简化到不能再简化。

请注意,以下所有程序的类别阶层架构、类别名称、变量名称、结构名称、函数名称、函数行为,都以MFC为仿真对象,具体而微。也可以说,我从数以万行计的MFC原代码中,「偷」了一些出来,砍掉旁枝末节,只露出重点。

在文件的安排上,我把仿真MFC的类别都集中在MFC.H和MFC.CPP 中,把自己衍生的类别集中在MY.H和MY.CPP中。对于自定类别,我的命名方式是在父类别的名称前面加一个" My" ,例如衍生自CWinApp者,名为CMyWinApp,衍生自CDocument者,名为CMyDoc。


MFC 类别阶层


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



这个实例仿真MFC 的类别阶层。后续数节中,我会继续在这个类别阶层上开发新的能力。在这些名为Frame?的各范例中,我以MFC源代码为蓝本,尽量仿真MFC的内部行为,并且使用完全相同的类别名称、函数名称、变量名称。这样的仿真对于我们在第三篇以及第四篇中深入探讨MFC 时将有莫大助益。相信我,这是真的。


Frame1范例程序

程序项目mfc31

Frame1 的执行结果是:

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函数之前完成的。

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


MFC程序的初始化过程


MFC 程序也是个Windows 程序,它的内部一定也像第1章所述一样,有窗口注册动作,有窗口产生动作,有消息循环动作,也有窗口函数。此刻我并不打算做出Windows 程序,只是想交待给你一个程序流程,这个流程正是任何MFC 程序的初始化过程的简化。以下是Frame2 范例程序的类别阶层及其成员。对于那些「除了构造式与析构式之外没有其它成员」的类别,我就不在图中展开他们了:



就如我曾在第1章解释过的,InitApplication 和InitInstance  现在成了MFC 的CWinApp的两个虚拟函数。前者负责「每一个程序只做一次」的动作,后者负责「每一个执行个体都得做一次」的动作。通常,系统会(并且有能力)为你注册一些标准的窗口类别(当然也就准备好了一些标准的窗口函数),你(应用程序设计者)应该在你的CMyWinApp中改写InitInstance ,并在其中把窗口产生出来--  这样你才有机会在标准的窗口类别中指定自己的窗口标题和菜单。下面就是我们新的main 函数:

void main()
{

CWinApp* pApp = AfxGetApp();

pApp->InitApplication();
pApp->InitInstance();
pApp->Run();
}

其中pApp指向theApp全域对象。在这里我们开始看到了虚拟函数的妙用(还不熟练者请快复习第2章):




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



你看到了,这些函数什么正经事儿也没做,光只输出一个标识符串。我主要的目的是在让你先熟悉MFC程序的执行流程。


以下就是Frame2 的执行结果:

CWinApp::InitApplication
CMyWinApp::InitInstance
CMyFrameWnd::CMyFrameWnd
CFrameWnd::Create
CWnd::CreateEx
CFrameWnd::PreCreateWindow
CWinApp::Run
CWinThread::Run


RTTI(执行时期型别辨识)(通过运行时类型识别

Runtime Type Identification

你已经在第2章看到,Visual C++ 4.0 支持RTTI,重点不外乎是:

1. 编译时需选用/GR选项(/GR  的意思是enable C++ RTTI
2. 包含typeinfo.h
3. 使用新的typeid运算子。


MFC 早在编译器支持RTTI之前,就有了这项能力。我们现在要以相同的手法,在DOS程序中仿真出来。我希望我的类别库具备IsKindOf的能力,能在执行时期侦测某个对象是否「属于某种类别」,并传回TRUE或FALSE 。以前一章的Shape为例,我希望:

CSquare* pSquare = new CSquare;
cout << pSquare->IsKindOf(CSquare); //  应该获得1 (TRUE)
cout << pSquare->IsKindOf(CRect); // 应该获得1 (TRUE)
cout << pSquare->IsKindOf(CShape); // 应该获得1 (TRUE)
cout << pSquare->IsKindOf(CCircle); //  应该获得0 (FALSE)


以MFC 的类别阶层来说,我希望:

CMyDoc* pMyDoc = new CMyDoc;
cout << pMyDoc->IsKindOf(CMyDoc); // 应该获得1 (TRUE)
cout << pMyDoc->IsKindOf(CDocument); // 应该获得1 (TRUE)
cout << pMyDoc->IsKindOf(CCmdTarget); // 应该获得1 (TRUE)
cout << pMyDoc->IsKindOf(CWnd); // 应该获得0 (FALSE)


注意:真正的IsKindOf  参数其实没能那么单纯


类别型录网与CRuntimeClass


怎么设计RTTI 呢?让我们想想,当你手上握有一种色泽,想知道它的RGB成份比,不查色表行吗?当你持有一种产品,想知道它的型号,不查型录行吗?要达到RTTI 的能力,我们(类别库的设计者)一定要在类别构造起来的时候,记录必要的信息,以建立型录。型录中的类别信息,最好以串行(linked list)方式串接起来,将来方便一一比对。

我们这份「类别型录」的串行元素将以CRuntimeClass 描述之,那是一个结构,内中至少需有类别名称、串行的Next 指针,以及串行的First指针。由于First 指针属于全域变量,一份就好,所以它应该以static修饰之。除此之外你所看到的其它CRuntimeClass成员都是为了其它目的而准备,陆陆续续我会介绍出来。

// in MFC.H
struct CRuntimeClass
{
	// Attributes
	LPCSTR 	m_lpszClassName;
	int 	m_nObjectSize;
	UINT 	m_wSchema; 			// schema number of the loaded class
	CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
	CRuntimeClass* 		m_pBaseClass ;
	// CRuntimeClass objects linked together in simple list
	static CRuntimeClass*  	pFirstClass ; 	// start of class list
	CRuntimeClass* 		m_pNextClass ; 	// linked list of registered classes
};


我希望,每一个类别都能拥有这样一个CRuntimeClass 成员变量,并且最好有一定的命名规则(例如在类别名称之前冠以" class"  作为它的名称),然后,经由某种手段将整个类别库构造好之后,「类别型录」能呈现类似这样的风貌:





DECLARE_DYNAMIC / IMPLEMENT_DYNAMIC宏


为了神不知鬼不觉把CRuntimeClass对象塞到类别之中,并声明一个可以抓到该对象地址的函数,我们定义DECLARE_DYNAMIC 宏如下:
#define DECLARE_DYNAMIC (class_name) \
public: \
        static CRuntimeClass class##class_name; \
        virtual CRuntimeClass* GetRuntimeClass() const;


出现在宏定义之中的##,用来告诉编译器,把两个字符串系在一起。如果你这么使用此宏:
DECLARE_DYNAMIC(CView)


编译器前置处理器为你做出的码是:
public:
        static CRuntimeClass classCView;
        virtual CRuntimeClass* GetRuntimeClass() const;


这下子,只要在声明类别时放入DECLARE_DYNAMIC 宏即万事OK喽。



不,还没有OK,类别型录(也就是各个CRuntimeClass对象
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值