学习《深入浅出MFC》总结(上)

来源:互联网 发布:在线对对联软件 编辑:程序博客网 时间:2024/05/16 19:00
 

看到现在,基本对这本书的主要内容有了一定的了解,好像有很多东西要总结,又觉得理不出头绪,还是简短点吧:

第一、   侯老师对MFC的认识确实是深入骨髓;

第二、   对RTTI、dynamic creation、serialization和Document/View的理解非常深厚;

第三、   对消息映射和命令传递的本质理解非常深厚;第二、三点是全书的重点内容

第四、   侯老师书中的内容有错误。这些错误的可能原因是,我阅读这本书是对照VC++6.0的MFC源码,而侯老师参照的是VC++5.0的MFC。我不清楚这两者是否有区别(现在哪找5.0啊),但是可以肯定,书中有些内容与6.0对不上。详细不同我在本文中有说明

 

 

第0章     你一定要知道(导读)

 

1、DDX,即Dialog Data eXchange。目的是在于简化应用程序取得控件内容的过程。DDV,即Dialog Data Validation。目的是在于加强对控件内容的数值进行合理化检查(只能做很简单的检查)。

 

2、MFC的坐标系统,即映射方式Mapping Mode,关系到是否能够“所见即所得”。

 

3、三个能称得上是Application Framework的东东:微软的MFC、Borland的OWL和IBM的Open Class Library。

 

第1章     Win32基本程序概念

 

1、UI(用户接口)资源包括菜单、对话框、图标、光标等,以各种文件形式存在,如. ico,.bmp,.cur等,然后通过一个资源描述文件.rc用文字对其进行描述。最后通过资源编译器将其编译为一个.res的文件,这个文件与程序二 进制目标代码文件.obj一起形成.exe文件。

 

2、Windows程序运行需要调用C Runtime库以及Windows API库中的函数,因此,需要的动态链接函数库包括:C Runtime静态链接版本(LIBC.LIB)或动态链接版本(MSVCRT.LIB)和Windows API的三大模块:GDI32.LIB、USER32.LIB和KERNEL32.LIB。

 

3、要使用Windows API需要包含Windows.h头文件。

 

4、Windows程序是以消息为基础,以事件来驱动的(Message Based, Event Driven)。消息存在于两个消息队列中:系统消息队列和应用程序消息队列。消息的产生有三种情况:一是系统硬件产生的消息,如鼠标,键盘等,放入到系 统消息队列中;二是其他窗口程序产生的消息,放入到应用程序消息队列中;三是用户自己的模块发送的消息,如果是用PostMessage发送的,就放入到 应用程序消息队列中,如果是用SendMessage发送的,则不进消息队列,直接给窗口函数处理。Windows程序的本体与操作系统的关系可用下图表 示:

 

5、函数调用习惯:CALLBACK(_stdcall),_pascal和_cdecl。

 

6、callback函数由用户设计,但永远不会也不应该由用户自己调用,它们是由Windows系统调用的。为什么窗口函数要设计为callback函数,而不由应用程序自己调用呢?因为这需要操作系统的统一管理多个程序,来合理分配调用时间。

 

7、MFC中消息映射的雏形:先定义一个结构体

struct MSGMAP_ENTRY

{

       UINT nMessage;

       LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);

};    //该结构体将一个消息与该消息的处理函数连在一起

//下面再定义一个宏,计算总共的消息数量

#define dim(x) (sizeof(x)/sizeof(x[0]))

再定义两个数组,一个是_messageEntries[]用来存放系统消息和消息处理函 数,另一个是_commandEntries[]用来存放WM_COMMAND消息的命令command_ID和命令处理函数。这样在窗口函数中不需要 switch/case了,只需要一个for循环就可以处理这些消息,而且可以不需要修改就可以处理新加入的消息,所要做的就是管理好上面的两个数组并定 义好消息处理函数。

 

8、C Runtime函数库的版本分为单线程和多线程两种,具体一共有6个可选版本:/ML、/MT、/MD、/MLd、/MTd、/MDd。使用MFC必须要用多线程版

 

9、一个Windows程序在启动的时候实际上由shell调用CreateProcess 函数创建了一个进程,进而再创建一个主线程。进程和线程属于系统的核心对象,共有6种核心对象:event、mutex、semaphore、 file/file-mapping、process、thread。

 

10、线程优先级范围从0到31,分两步设定:先在CreateProcess的dwCreationFlags中设定“优先级等级priority class”,后在SetThreadPriority中进行微调。

 

11、一般的SDK API编程中,在InitApplication中进行WNDCLASS的注册,在InitInstance中调用CreateWindow创建窗口。 注:但是在MFC中,窗口类的注册和窗口的创建都是在InitInstance函数中进行的,实际上是在CWnd的Create函数中完成的(先调用 PreCreateWindow注册窗口,后调用CreateWindowEx创建),详细在后面章节介绍。

 

第2章     C++的重要性质

 

1、IsKindOf用来在MFC中进行运行时类型识别(RTTI)。但它并没有使用C++ 语言所支持的RTTI的typeid等操作,它有自己的一套RTTI方法,与一组神秘的宏和一个类有关,它们是DECLARE_DYNAMIC、 IMPLEMENT_DYNAMIC和CRuntimeClass。

 

2、类的静态数据成员初始化应放在该类的程序文件.cpp中进行,且应在任何函数之外,不能放在构造函数中,构造函数会被调用多次。

 

3、我看得C++书籍(指C++ Primer)并没有讲述如何实现多态性。在这本书中简单介绍了如何通过一个虚拟函数表,并在表中放置实际要调用的函数地址来实现的。

 

4、我在C++的书籍当中并没有看到关于动态创建和永久保存的相关概念(书中说这两个概念属于面向对象的范畴),这两个概念在标准C++语言中并没有包含。但是在MFC中实现了这两个概念的功能。

 

 

注:这一章所讲的知识点,大多在学习C++的时候就已经掌握了,所以没有太多总结。不过,学习MFC确确实实需要扎实的C++基础,否则是很难深入地对MFC进行理解的。

 

 

第3章     MFC六大关键技术之仿真

 

1、六大关键技术包括:MFC程序的初始化过程、RTTI运行时类型识别、Dynamic Creation动态创建、Persistence永久保存、Message Mapping消息映射和Message Routing消息传递(应该是命令传递,标准消息WM_XXX的传递是简单的从子类向父类传)

 

2、RTTI的仿真:C++语言具有RTTI的能力,是通过虚函数表来实现的。在MFC中有自己的实现方式

 

3、RTTI的实现(书中所讲与VC6.0对不上,我是按照书上所讲总结的)

原理过程:

3.1   为每一个类定义一个静态的CRuntimeClass结构对象,该结构的定义如下:

struct CRuntimeClass

{

       LPCSTR m_lpszClassName;        //相应的C++类的名称

       Int   m_nObjectSize;                    //类的大小

       UINT      m_wSchema;                //类的版本号

       CObject * (PASCAL * m_pfnCreateObject)();     //创建类所用的函数指针

       CRuntimeClass * m_pBaseClass;                 //该类的基类的CRuntimeClass结构对象地址

       Static CRuntimeClass * pFirstClass;            //第一个CRuntimeClass结构对象地址

       CRuntimeClass * m_pNextClass;                //下一个CRuntimeClass结构对象地址

};    //再强调一下,这里与VC6.0的MFC源码对不上

3.2   将所有类的CRuntimeClass连接成一个网(书中称为“类别型录网”,我称为“类的类型网”)。连接的方式有两种:第一,以基类和子类为线索,用 数据成员m_pBaseClass将类连接起来,构成这个网的经线(这是我起的名字,实际上要实现RTTI只需要经线);第二,以类产生的先后顺序,用数 据成员m_pNextClass将类连接起来,构成这个网的纬线(实际在VC6.0当中,多数情况下没有建立纬线,而且RTTI也不需要纬线,又对不 上);

3.3   定义一个IsKindOf函数,从子类沿着经线向基类搜索,从而实现RTTI。

 

4、如何建立“类的类型网”:如上所述可知,建立“类的类型网”是RTTI的关键,实际上也是动态创建和永久保存的关键步骤。可分两步:

4.1   在类的定义中,加上DECLARE_DYNAMIC宏,该宏的定义如下:

#define _DECLARE_DYNAMIC(class_name) /

public: /

       static CRuntimeClass class##class_name; /                //一定要是static,每个类只能有一个

       virtual CRuntimeClass* GetRuntimeClass() const; /     //一定要是virtual,否则不能RTTI

4.2   在类的实现中,加上IMPLEMENT_DYNAMIC宏,该宏的定义如下:

#define IMPLEMENT_DYNAMIC(class_name, base_class_name) /

       IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)

 

而IMPLEMENT_RUNTIMECLASS的定义如下:

#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) /

 

       const CRuntimeClass class_name::class##class_name = { /

              #class_name, sizeof(class class_name), wSchema, pfnNew, /

                     RUNTIME_CLASS(base_class_name), NULL }; / 

//初始化CRuntimeClass结构,并建立起类型网的经线

 

       static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name); /

//建立起类型网的纬线,在VC6.0中并没有这一条

 

       CRuntimeClass* class_name::GetRuntimeClass() const /

              { return RUNTIME_CLASS(class_name); } /      //定义virtual函数

 

其中,RUNTIME_CLASS宏的定义如下:

#define RUNTIME_CLASS(class_name) /

((CRuntimeClass*)(&class_name::class##class_name))/

 

 

具体RTTI的用法:pView->IsKindOf(RUNTIME_CLASS(CView));

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const

{

       CRuntimeClass* pClassThis = GetRuntimeClass();

       while (pClassThis != NULL)

       {

              if (pClassThis == pClass)

                     return TRUE;

              pClassThis = pClassThis->m_pBaseClass;

       }

       return FALSE;

}

注:MFC中的这种实现RTTI的方式是否比C++中的高效合理,不一定。但是起码MFC这种方式已经使用很长时间了。

 

5、动态创建:这个功能的思路是指当遇到某种情况,即如果知道一个类的名字,如何创建出该类 的一个对象。它的基本方法也是借助CRuntimeClass来实现的。但是侯先生所讲仍然与VC6.0的差别很大。其实动态创建有两种方式:一种是这样 的((CRuntimeClass *) RUNTIME_CLASS(CMyView))->CreateObject(),这种方式是利用RUNTIME_CLASS宏直接放类的名字 CMyView;另一种使用方式是想通过CString strClassName来创建一个类。

 

如果是用第一种方式就很简单,只需要将RTTI所用的宏改为:DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE。

#define DECLARE_DYNCREATE(class_name) /

       DECLARE_DYNAMIC(class_name) /

       static CObject* PASCAL CreateObject();     //创建函数,会由CRuntimeClass调用

 

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) /

       CObject* PASCAL class_name::CreateObject() /

              { return new class_name; } /        //定义创建函数为new一个类

       IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, /

              class_name::CreateObject)            //注意这里将创建函数的指针给了CRuntimeClass

其实这种方式的动态创建根本不需要类型网的纬线,甚至经线都不用,因为直接就可以获取CRuntimeClass,然后调用CreateObject就可以了。

 

而第二种方式必须要用到类型网中的纬线,原理是沿着纬线,用strClassName与所有 CRuntimeClass中的类名称比较来获取CRuntimeClass,再CreateObject。可是前面我说过了,其实在VC6.0中根本就 没有建立起类型网的纬线,所以是肯定不能实现第二种方式的动态创建的。网上很多人在问这个问题,我想一定是没搞明白这一点。那么如何使得可以实现第二种方 式的动态创建呢?很简单,修改宏,把纬线的建立那一句加上去就可以了。然后,可以这样:

       AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

       AfxLockGlobals(CRIT_RUNTIMECLASSLIST);

       for (pClass = pModuleState->m_classList; pClass != NULL;

              pClass = pClass->m_pNextClass)

       {

              if (lstrcmpA(strClassName, pClass->m_lpszClassName) == 0)

              {

                     AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);

                     return pClass;

              }

       }

       AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);

       return NULL;

具体一些细节就不解释了。

 

6、Persistence(永久保存)机制:我大概看了看,觉得不能理解微软怎么会设计成 这样呢?这真的是一个Application Framework所需要的吗?为什么C++不直接支持呢?其他的Application Framework也是这样的吗?我不想搞清楚它的原理了。但是有一点说明:用DECLARE_SERIAL和IMPLEMENT_SERIAL建立起了 类型网的经线和纬线,是可以实现两种方式的动态创建的。

 

7、消息映射的实现:消息映射网实际上类似于上面说的“类的类型网”,它使得每一个类都包含一个消息数组结构(将消息和消息处理函数连起来),并将其以基类和子类为线索连接起来,形成一个消息映射网,它只有经线。如下图所示:

注意在CWinApp中的消息映射跳过了CWinThread,直接连到了 CCmdTarget类,所用到的宏包括在类定义中使用的DECLARE_MESSAGE_MAP和在类实现中使用的 BEGIN_MESSAGE_MAP、ON_COMMAND和END_MESSAGE_MAP。记住,只有经线,没有纬线。

 

8、命令传递的实现:刚才讲的消息映射实现了从子类到基类的消息映射网,但是实际上在MFC中命令消息的传递并非如此,消息可以横向流动,它靠的是一个消息驱动泵,这个泵是由一些函数组成的。这里不细说了。

 

9、MFC的消息循环规律:

9.1   如果是一般的Windows消息(WM_xxx),则一定是由派生类流向基类,没有旁流的可能;

9.2   如果是命令消息WM_COMMAND,那就有奇特的路线:

 

注:看到现在(2007-07-04),感觉侯捷在这一章讲的有些不好,因为他所仿真出来的RTTI、动态创建与实际的Visual C++6.0版有一些地方不相同,使得他所讲的一些功能在实际当中并不能实现或存在错误,这在网上已经看到很多了。

 

 

第4章     Visual C++集成开发环境

 

1、利用VC可开发的应用软件类型包括:MFC-based EXE、MFC-based DLL、Win32 Application(EXE)、Win32 Dynamic Link Library(DLL)、Win32 Console Applications、MFC ActiveX Controls、ATL COM(ActiveX Template Library Component Object Model)、ISAPI(Internet Server API) Extension Application和Win32 Static Library

 

2、使用TRACE宏可在VC中调试程序时,输出一个字符串。同样使用afxDump也可以输出一个字符串或其他数值到调试窗,比TRACE宏要方便

 

3、在MFC中利用TRY{…}CATCH(CException, e){…}等等这样的宏来实现异常处理。与C++语言中的try{…}catch(exception)有些不同

 

4、AppWizard产生的程序骨干框架是不能改变的,要改变只能有两个方法:重新产生一个新程序;修修补补很难搞定