MFC深入-1.MFC是一个编程框架
MFC概述MFC是一个编程框架MFC (Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架它的目的就是让程序员在此基础上来建立Windows下的应用程序这是一种相对SDK来说更为简单的方法。因为总体上MFC框架定义了应用程序的轮廓并提供了用户接口的标准实现方法程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C提供了相应的工具来完成这个工作AppWizard可以用来生成初步的框架文件代码和资源等资源编辑器用于帮助直观地设计用户接口ClassWizard用来协助添加代码到框架文件最后编译则通过类库实现了应用程序特定的逻辑。封装构成MFC框架的是MFC类库。MFC类库是C类库。这些类或者封装了Win32应用程序编程接口或者封装了应用程序的概念或者封装了OLE特性或者封装了ODBC和DAO数据访问的功能等等分述如下。1对Win32应用程序编程接口的封装用一个C Object来包装一个Windows Object。例如class CWnd是一个C window object它把Windows window(HWND)和Windows window有关的API函数封装在C window object的成员函数内后者的成员变量m_hWnd就是前者的窗口句柄。2对应用程序概念的封装使用SDK编写Windows应用程序时总要定义窗口过程登记Windows Class创建窗口等等。MFC把许多类似的处理封装起来替程序员完成这些工作。另外MFC提出了以文档-视图为中心的编程模式MFC类库封装了对它的支持。文档是用户操作的数据对象视图是数据操作的窗口用户通过它处理、查看数据。3对COM/OLE特性的封装OLE建立在COM模型之上由于支持OLE的应用程序必须实现一系列的接口Interface因而相当繁琐。MFC的OLE类封装了OLE API大量的复杂工作这些类提供了实现OLE的更高级接口。4对ODBC功能的封装以少量的能提供与ODBC之间更高级接口的C类封装了ODBC API的大量的复杂的工作提供了一种数据库编程模式。继承首先MFC抽象出众多类的共同特性设计出一些基类作为实现其他类的基础。这些类中最重要的类是CObject和CCmdTarget。CObject是MFC的根类绝大多数MFC类是其派生的包括CCmdTarget。CObject 实现了一些重要的特性包括动态类信息、动态创建、对象序列化、对程序调试的支持等等。所有从CObject派生的类都将具备或者可以具备CObject所拥有的特性。CCmdTarget通过封装一些属性和方法提供了消息处理的架构。MFC中任何可以处理消息的类都从CCmdTarget派生。针对每种不同的对象MFC都设计了一组类对这些对象进行封装每一组类都有一个基类从基类派生出众多更具体的类。这些对象包括以下种类窗口对象基类是CWnd应用程序对象基类是CwinThread文档对象基类是Cdocument等等。程序员将结合自己的实际从适当的MFC类中派生出自己的类实现特定的功能达到自己的编程目的。虚拟函数和动态约束MFC以“C”为基础自然支持虚拟函数和动态约束。但是作为一个编程框架有一个问题必须解决如果仅仅通过虚拟函数来支持动态约束必然导致虚拟函数表过于臃肿消耗内存效率低下。例如CWnd封装 Windows窗口对象时每一条Windows消息对应一个成员函数这些成员函数为派生类所继承。如果这些函数都设计成虚拟函数由于数量太多实现起来不现实。于是MFC建立了消息映射机制以一种富有效率、便于使用的手段解决消息处理函数的动态约束问题。这样通过虚拟函数和消息映射MFC类提供了丰富的编程接口。程序员继承基类的同时把自己实现的虚拟函数和消息处理函数嵌入MFC的编程框架。MFC编程框架将在适当的时候、适当的地方来调用程序的代码。本书将充分的展示MFC调用虚拟函数和消息处理函数的内幕让读者对MFC的编程接口有清晰的理解。MFC的宏观框架体系如前所述MFC实现了对应用程序概念的封装把类、类的继承、动态约束、类的关系和相互作用等封装起来。这样封装的结果对程序员来说是一套开发模板或者说模式。针对不同的应用和目的程序员采用不同的模板。例如SDI应用程序的模板MDI应用程序的模板规则DLL应用程序的模板扩展DLL应用程序的模板OLE/ACTIVEX应用程序的模板等等。这些模板都采用了以文档-视为中心的思想每一个模板都包含一组特定的类。典型的MDI应用程序的构成将在下一节具体讨论。为了支持对应用程序概念的封装MFC内部必须作大量的工作。例如为了实现消息映射机制MFC编程框架必须要保证首先得到消息然后按既定的方法进行处理。又如为了实现对DLL编程的支持和多线程编程的支持MFC内部使用了特别的处理方法使用模块状态、线程状态等来管理一些重要信息。虽然这些内部处理对程序员来说是透明的但是懂得和理解MFC内部机制有助于写出功能灵活而强大的程序。总之MFC封装了Win32 APIOLE APIODBC API等底层函数的功能并提供更高一层的接口简化了Windows编程。同时MFC支持对底层API的直接调用。MFC提供了一个Windows应用程序开发模式对程序的控制主要是由MFC框架完成的而且MFC也完成了大部分的功能预定义或实现了许多事件和消息处理等等。框架或者由其本身处理事件不依赖程序员的代码或者调用程序员的代码来处理应用程序特定的事件。MFC是C类库程序员就是通过使用、继承和扩展适当的类来实现特定的目的。例如继承时应用程序特定的事件由程序员的派生类来处理不感兴趣的由基类处理。实现这种功能的基础是C对继承的支持对虚拟函数的支持以及MFC实现的消息映射机制。MDI应用程序的构成本节解释一个典型的MDI应用程序的构成。用AppWizard产生一个MDI工程t无OLE等支持AppWizard创建了一系列文件构成了一个应用程序框架。这些文件分四类头文件.h实现文件(.cpp)资源文件(.rc)模块定义文件(.def)等。构成应用程序的对象图1-1解释了该应用程序的结构箭头表示信息流向。从CWinApp、CDocument、CView、CMDIFrameWnd、CMDIChildWnd类对应地派生出CTApp、CTDoc、CTView、CMainFrame、CChildFrame五个类这五个类的实例分别是应用程序对象、文档对象、视对象、主框架窗口对象和文档边框窗口对象。主框架窗口包含了视窗口、工具条和状态栏。对这些类或者对象解释如下。1应用程序应用程序类派生于CWinApp。基于框架的应用程序必须有且只有一个应用程序对象它负责应用程序的初始化、运行和结束。2边框窗口如果是SDI应用程序从CFrameWnd类派生边框窗口类边框窗口的客户子窗口(MDIClient)直接包含视窗口如果是MDI应用程序从CMDIFrameWnd类派生边框窗口类边框窗口的客户子窗口(MDIClient)直接包含文档边框窗口。如果要支持工具条、状态栏则派生的边框窗口类还要添加CToolBar和CStatusBar类型的成员变量以及在一个OnCreate消息处理函数中初始化这两个控制窗口。边框窗口用来管理文档边框窗口、视窗口、工具条、菜单、加速键等协调半模式状态如上下文的帮助(SHIFTF1模式)和打印预览。3文档边框窗口文档边框窗口类从CMDIChildWnd类派生MDI应用程序使用文档边框窗口来包含视窗口。4文档文档类从CDocument类派生用来管理数据数据的变化、存取都是通过文档实现的。视窗口通过文档对象来访问和更新数据。5视视类从CView或它的派生类派生。视和文档联系在一起在文档和用户之间起中介作用即视在屏幕上显示文档的内容并把用户输入转换成对文档的操作。6文档模板文档模板类一般不需要派生。MDI应用程序使用多文档模板类CMultiDocTemplateSDI应用程序使用单文档模板类CSingleDocTemplate。应用程序通过文档模板类对象来管理上述对象应用程序对象、文档对象、主边框窗口对象、文档边框窗口对象、视对象的创建。构成应用程序的对象之间的关系这里用图的形式可直观地表示所涉及的MFC类的继承或者派生关系如图1-2所示意。图1-2所示的类都是从CObject类派生出来的所有处理消息的类都是从CCmdTarget类派生的。如果是多文档应用程序文档模板使用CMultiDocTemplae主框架窗口从CMdiFarmeWnd派生它包含工具条、状态栏和文档框架窗口。文档框架窗口从CMdiChildWnd派生文档框架窗口包含视视从CView或其派生类派生。构成应用程序的文件通过上述分析可知AppWizard产生的MDI框架程序的内容所定义和实现的类。下面从文件的角度来考察AppWizard生成了哪些源码文件这些文件的作用是什么。表1-1列出了AppWizard所生成的头文件表1-2列出了了AppWizard所生成的实现文件及其对头文件的包含关系。表1-1 AppWizard所生成的头文件头文件用途stdafx.h标准AFX头文件resource.h定义了各种资源IDt.h#include resource.h定义了从CWinApp派生的应用程序对象CTAppchildfrm.h定义了从CMDIChildWnd派生的文档框架窗口对象CTChildFramemainfrm.h定义了从CMDIFrameWnd派生的框架窗口对象CMainFrametdoc.h定义了从CDocument派生的文档对象CTDoctview.h定义了从CView派生的视图对象CTView表1-2 AppWizard所生成的实现文件实现文件所包含的头文件实现的内容和功能stdafx.cpp#include stdafx.h用来产生预编译的类型信息。t.cpp# include stdafx.h# include t.h# include MainFrm.h# include childfrm.h#include tdoc.h#include tview.h定义CTApp的实现并定义CTApp类型的全局变量theApp。childfrm.cpp#inlcude stdafx.h#include t.h#include “childfrm.h”实现了类CChildFramechildfrm.cpp#inlcude stdafx.h#include t.h#include childfrm.h实现了类CMainFrametdoc.cpp# include stdafx.h# include t.h# include tdoc.h实现了类CTDoctview.cpp# include stdafx.h# include t.h# include tdoc.h# include tview.h实现了类CTview从表1-2中的包含关系一栏可以看出CTApp 的实现用到所有的用户定义对象包含了他们的定义CView 的实现用到CTdoc其他对象的实现只涉及自己的定义当然如果增加其他操作引用其他对象则要包含相应的类的定义文件。对预编译头文件说明如下所谓头文件预编译就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译以后该工程编译时不再编译这部分头文件仅仅使用预编译的结果。这样可以加快编译速度节省时间。预编译头文件通过编译stdafx.cpp生成以工程名命名由于预编译的头文件的后缀是“pch”所以编译结果文件是projectname.pch。编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为所有在指令#include stdafx.h前的代码都是预编译的它跳过#include stdafx. h指令使用projectname.pch编译这条指令之后的所有代码。因此所有的CPP实现文件第一条语句都是#include stdafx.h。另外每一个实现文件CPP都包含了如下语句#ifdef _DEBUG#undef THIS_FILEstatic char BASED_CODE THIS_FILE[] __FILE__;#endif这是表示如果生成调试版本要指示当前文件的名称。__FILE__是一个宏在编译器编译过程中给它赋值为当前正在编译的文件名称。