MFC应用程序框架入门(转)
1 MFC概述
顾名思意,MFC应用程序框架是以MFC作为框架基础的,以此程序框架模式搭建起来的应用程序在程序结构组织上是完全不同于以前的Win32 SDK编程方式的。自20世纪90年代初问世以来,MFC一直试图把Windows API函数封装到类库中个各个逻辑类中。MFC的这种封装并非简单地对API函数进行分组与打包,而是更多地通过类来试图实现全部的系统策略。随着越来越多系统功能的加入,MFC的规模也在不断拓展,目前已包括有200多个类,涵盖了通用Windows 类、文档/视框架、OLE、数据库、Internet以及分布式功能等多方面的基本内容。这样一个坚实的程序开发基础无疑从很大程度上方便了程序设计人员对Windows 程序的开发。
MFC提供了相当多不同功能的类以适合尽可能广泛的需求。这里绝大多数的MFC类都是直接或间接从CObject类派生出来的,CObject类为其派生类提供了三个重要的特性支持:持久性(Serialization)支持、运行时(Run-time)类信息支持和诊断(Diagnostic)调试支持等。其中持久性是以流的方式将某个类对象中的持久性数据输出或输入到外部存储介质如磁盘文件等的过程;运行时类信息(Run-time Class Information,RTCI)则可以重新获取一个对象的类名及其他一些有关对象在运行时的信息。RTCI也是C++中除运行时类型信息(Run-time Type Information,RTTI)机制外的另一个重要工具;诊断和调试支持作为CObject类的一个组成部分,可以在实现CObject派生类时执行有效性检查并可向调试窗口输出状态信息。
并非MFC提供的所有函数都是类成员函数,MFC也提供了一系列以Afx为前缀的全局函数。类成员函数只能在其所属类对象所在的上下文中使用,但是这些AFX函数却可以在任何时候的任何地方直接使用。下表列出的是几个比较重要AFX函数:
函数名 | 函数说明 |
AfxAbout | 无条件终止一个应用程序;通常在发生无法回复的错误时使用 |
AfxBeginThread | 创建一个新的线程并开始执行 |
AfxEndThread | 终止当前正在执行的线程 |
AfxMessageBox | 显示一个Windows 消息窗口 |
AfxGetApp | 返回一个指向应用程序对象的指针 |
AfxGetAppName | 返回应用程序名 |
AfxGetMainWnd | 返回一个指向应用程序主窗口的指针 |
AfxGetInstanceHandle | 返回一个标识当前应用程序实例的句柄 |
AfxRegisterWndClass | 为一个MFC应用程序注册一个用户自定义的窗口类 |
2 MFC对API函数的封装
如果读者曾经有过SDK的开发经历,一定会对其烦琐的编程方式和大量的Win32 API函数调用深有感触。所有不同功能的API函数均是以全局函数的形式放在一起的,由于API函数数目比较庞大,因此无论是学习还是使用都是有一定难度的。相比而言,建立在API函数基础之上的MFC类库则通过把相关API函数的分类封装而可以大大简化编程的难度,用MFC类编写的Windows 应用程序完成相同的任务只需要进行少量的工作。
众多的API函数根据功能的不同而被MFC封装到200多个类中,这些类基本涵盖了进行Windows 编程大部分可能用到的功能。由于封装后的MFC类太多,这里不能一一介绍,下面就以其中比较重要的CObject类和CWnd类为例对API函数的封装情况做一简要介绍。
CObject类是MFC中最主要也是最基本的类之一,该类不支持多重继承,派生的类只能有一个CObject基类。CObject类是位于类层次结构最顶层的,绝大多数MFC类都是从CObject类派生出来的。CObject类包含了所有MFC类必须具备的几个基本功能:持久性支持、运行时类信息支持和诊断调试支持。其中持久性支持功能由成员函数IsSerializable()和Serialize()提供。前者用于检测对象是否支持序列化。如果一个类能够被序列化,就必须在声明时包含DECLARE_SERIAL宏、在实现时包含IMPLEMENT_SERIAL宏。Serialize()函数则可以将对象写入档案文件(Archive)或从档案文件读出对象。成员函数GetRuntimeClass()可以获取到一个指向CruntimeClass类对象的指针,通过该指针可以得到对象的运行时类信息。CObject类在诊断调试支持方面提供了成员函数AssertValid()和Dump(),前者可对对象内存状态的有效性进行检查,后者负责将对象的内容转储到一个CdumpContext对象中,并可以提供诊断服务及一些有用的调试信息。
在MFC中,CWnd类提供了所有窗口类的基本功能,是一个非常重要的类,大约三分之一的MFC类都是以此为基类。该类主要对创建、操纵窗口类的API函数进行了封装,而且通过消息映射机制隐藏了SDK编程中使用相当不便的窗口处理函数,是消息的分发处理更加方便。
CWnd类最重要的一个封装是对API函数CreateWindow()的封装,该函数被封装为CWnd类成员函数Create()。从VC提供的MFC源文件WinCore.cpp中可以清楚看出CWnd类对CreateWindow()函数的封装过程,下面给出相关部分的实现清单:
BOOL CWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) { // can't use for desktop or pop-up windows (use CreateEx instead) ASSERT(pParentWnd != NULL); ASSERT((dwStyle & WS_POPUP) == 0); return CreateEx(0, lpszClassName, lpszWindowName, dwStyle | WS_CHILD, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), (HMENU)nID, (LPVOID)pContext); } |
可以看出,主要工作是在CreateEx()成员函数中完成的,而该函数又是对API函数CreateWindowEx()的封装。封装后的代码在调用CreateWindowEx()前构造并填充了一个非常类似于WNDCLASS结构的CREATESTRUCT结构,并调用了PreCreateWindow()。
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam) { // allow modification of several common create parameters CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.lpszClass = lpszClassName; cs.lpszName = lpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu; cs.hInstance = AfxGetInstanceHandle(); cs.lpCreateParams = lpParam; if (!PreCreateWindow(cs)) { PostNcDestroy(); return FALSE; } AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams); #ifdef _DEBUG if (hWnd == NULL) { TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X\n", GetLastError()); } #endif if (!AfxUnhookWindowCreate()) PostNcDestroy(); // cleanup if CreateWindowEx fails too soon if (hWnd == NULL) return FALSE; ASSERT(hWnd == m_hWnd); // should have been set in send msg hook return TRUE; } |
看上去经过封装的窗口创建函数要比原API函数复杂许多,但这并不说明MFC的封装将导致编程的效率低下,恰恰相反,由于CWnd在绝大多数场合中是以基类的形式出现的,因此可在派生类中添加代码完成对CWnd::Create()的调用而比较方便的实现对派生类窗口的创建。
3 MFC应用程序框架
MFC应用程序框架可以看作是MFC基本类库的一个超集(Superset),类库是众多可在任何程序中使用的类的集合,而应用程序框架则定义了程序自身的结构。下面给出一个使用了MFC应用程序框架的简单例子,通过这段例程可以比较清楚地了解MFC应用程序框架的一般结构。
// Sample01.h文件 // 应用程序类 class CSample01App : public CWinApp { public: virtual BOOL InitInstance(); }; // 框架窗口类 class CSample01Frame : public CFrameWnd { public: CSample01Frame(); protected: afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() }; // Sample01.cpp文件 #include <afxwin.h> #include "Sample01.h" // 应用程序对象 CSample01App theApp; // 初始化应用程序实例 BOOL CSample01App::InitInstance() { m_pMainWnd = new CSample01Frame(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } // 消息映射 BEGIN_MESSAGE_MAP(CSample01Frame, CFrameWnd) ON_WM_PAINT() END_MESSAGE_MAP() // 构造函数 CSample01Frame::CSample01Frame() { Create(NULL, "MFC应用程序框架程序"); } // WM_PAINT消息响应函数 void CSample01Frame::OnPaint() { CPaintDC dc(this); dc.TextOut(100, 100, "Hello World!"); } |
仍象编写Sample00程序一样建立一个Win32应用程序工程Sample01(配套程序见"光盘\配套程序\Sample01\"),然后分别向工程添加头文件Sample01.h和源文件Sample01.cpp,并将上述代码写入相应的文件。为了能顺利编译,还需要修改一下编译命令,通过"Alt+F7"快捷键呼出【Project Settings】对话框,在【Preprocessor definitions】栏的最后添加选项"_AFXDLL",前面用逗号分隔。接下来还需要在【Project Options】栏的最后添加命令行"/MD",用空格同其他命令行参数进行分隔。编译运行,可以看出效果同SDK方式编写的Sample00程序是一样的,但在代码实现上更加结构化,编写过程也更加简单。
接下来,对上述应用程序框架代码进行分析。首先从MFC应用程序的核心--CWinApp类的派生类CSample01App谈起。CWinApp类提供了可以获取消息并将获取到的消息分发到应用程序窗口的消息循环和一些关键的虚函数,通过对这些虚函数的重载可使开发人员对应用程序的一些固有行为进行扩展。当把头文件Afxwin.h包含进来后,就可以在程序中使用包括CWinApp在内的一些MFC类了。一个MFC应用程序有且只能有一个应用程序对象而且必须被以全局方式进行声明,所以该对象自程序开始运行起就一直驻留在内存。
由于使用了MFC应用程序框架的程序在本质上仍是Windows 应用程序,因此必然需要在程序中存在作为Windows 应用程序入口的WinMain()函数。在前面的示例代码中之所以没有看到WinMain()函数是由于该函数已经通过封装的手段隐藏到应用程序框架中了。除WinMain()外,CWinApp类成员函数Run()也是隐含执行的,这个函数也是非常重要的,它负责把消息放进应用程序窗口的消息循环中,由WinMain()函数完成对Run()的调用 。当WinMain()函数寻找到应用程序对象后将立即调用CWinApp类的虚函数InitInstance()。由于CWinApp基类是不知道究竟需要何种主框架窗口的,因此在使用时必须在CWinApp的派生类中对InitInstance()函数进行重载。InitInstance()函数是在应用程序已经开始运行但窗口尚未创建时被调用的,若非由InitInstance()创建了窗口,应用程序是无法拥有窗口的,这也就意味着缺少了InitInstance()函数的应用程序将无法接收、处理消息,对Windows程序而言这也就失去了存在的意义。由此可见,从CWinApp类中进行派生,并且对InitInstance()函数进行重载是编写MFC应用程序框架程序的必要条件。
除应用程序类外,从CFrameWnd派生的CSample01Frame类还对应用程序的主框架窗口做了描述。在构造函数中调用基类的CFrameWnd成员函数Create(),由Windows 负责创建出实际的窗口结构,并由应用程序框架将其链接到C++对象。
本示例程序的大部分功能实际是在MFC的CWinApp和CFrameWnd等基类中完成的,在编程时,只需在派生类中编写少量功能代码,C++允许以这样的方式借用基类中的大量代码而无须复制代码。应用程序框架负责提供程序的结构框架,开发人员在此基础上为其添加相应的实现代码,从而可以非常方便地完成一个完整的应用程序。应用程序框架不仅定义了应用程序的结构安排,实际上还包含了更多的C++基类。
4 小结
SDK的API编程方法、MFC的编程方法以及本系列讲座后面将要介绍的ATL编程方法是VC++程序设计中比较常用的几种编程方法,其中MFC以其强大的功能和灵活的编程方式而成为大多数程序开发人员最经常使用的一种编程方式。本文从基础问题入手对MFC及其框架程序做了较为详细的论述,使读者能够对MFC编程有一个基本的认识。
转自:http://www.cnblogs.com/lidabo/archive/2012/11/23/2785030.html
MFC应用程序框架入门(转)的更多相关文章
- VS2010/MFC编程入门之四(MFC应用程序框架分析)
VS2010/MFC编程入门之四(MFC应用程序框架分析)-软件开发-鸡啄米 http://www.jizhuomi.com/software/145.html 上一讲鸡啄米讲的是VS2010应用 ...
- VS2010-MFC(MFC应用程序框架分析)
转自:http://www.jizhuomi.com/software/145.html 一.SDK应用程序与MFC应用程序运行过程的对比 程序运行都要有入口函数,在之前的C++教程中都是main函数 ...
- [转]深入理解MFC中程序框架
最近抽空复习了一下MFC的内容,觉得一篇博文写的不错. 原文内容太多直接给出链接吧:深入理解MFC中程序框架 链接2:深入浅出话VC++(2)——MFC的本质 链接3:MFC单文档/视图结构穷追猛打
- MFC应用程序框架(转)
对于程序员来说,如果要提高编程效率,一个好用的,功能强大的并且可以得心应手使用的编程工具往往会给我们程序员带来莫大的方便.其实对于现在的编程工具来说,使用哪一种工具都不是问题的关键,重要的是你能够使用 ...
- 第四课——MFC应用程序框架
一.MFC应用程序类型 上篇文章的彩蛋:可通过使用MFC应用程序向导(MFC AppWizard)的功能来创建所需要的应用程序,这意味着不需要输入任何代码.MFC除了应用程序向导,还对应用程序项目有着 ...
- windows应用程序框架及实例
应用程序框架:同一类型应用程序的结构大致相同,并有很多相同的源代码,因此可以通过一个应用程序框架AFX(Application FrameWorks)编写同一类型应用程序的通用源代码. 主要向导: D ...
- VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)
VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)-软件开发-鸡啄米 http://www.jizhuomi.com/software/141.html 上一讲中讲了VS20 ...
- VS2010/MFC编程入门之五十二(Ribbon界面开发:创建Ribbon样式的应用程序框架)
上一节中鸡啄米讲了GDI对象之画刷CBrush,至此图形图像的入门知识就讲完了.从本节开始鸡啄米将为大家带来Ribbon界面开发的有关内容.本文先来说说如何创建Ribbon样式的应用程序框架. Rib ...
- VS2010-MFC(利用MFC向导生成单文档应用程序框架)
一.VC++与MFC 讲VC++免不了要提MFC,MFC全称Microsoft Foundation Classes,也就是微软基础类库.它是VC++的核心,是C++与Windows API的结合,很 ...
随机推荐
- Queryable.GroupBy<TSource, TKey> 方法 (IQueryable<TSource>, Expression<Func<TSource, TKey>>) 转
根据指定的键选择器函数对序列中的元素进行分组. 命名空间: System.Linq程序集: System.Core(在 System.Core.dll 中) 语法 C# C++ F# VB p ...
- 《day06---面向对象入门》
/* java开发流程:思路. 案例:对数组操作.获取最大值. 思路: 1,一组数,要获取最大值,比较. 2,怎么比较?挨个比较,要获取数组中的每一个数据都要比较. 3,比较完,记录下来比较大的数据, ...
- hdu 2036
Ps: - -感觉这道题完全就是数学题...就是求知道每个顶点的坐标,然后求这个多边形的面积... 代码:#include "stdio.h"#include "std ...
- 深入理解:Android 编译系统
一,简介: Android Build 系统是用来编译 Android 系统,Android SDK 以及相关文档的一套框架.众所周知,Android 是一个开源的操作系统.Android 的源码中包 ...
- Ubuntu系统启用Apache Mod_rewrite模块
在终端中执行 sudo a2enmod rewrite 指令后,即启用了 Mod_rewrite 模块. 另外,也可以通过将 /etc/apache2/mods-available/rewrite.l ...
- JLOI2010 冠军调查 最小割
var b,f:..] of longint; s,t,i,j,n,m,x,y:longint; l,h:..]of longint; a:..,..]of longint; procedure bf ...
- [DP]数位DP总结
数位DP总结 By Wine93 2013.7 1.学习链接 [数位DP] Step by Step http://blog.csdn.net/dslovemz/article/details/ ...
- parse,tryparse区别
Convert.ToInt32.int.Parse(Int32.Parse).int.TryParse.(int) 四者都可以解释为将类型转换为 int,那它们的区别是什么呢? Convert.ToI ...
- 重学STM32---(十) ——CAN通信(二)
CAN协议是非常难的,但是在stm32中却是简单的,只需要我们配置寄存器即可,,,即使这样,我在学习的时候也遇到了许多困难 1.开时钟,不用说 2.设置GPIO口,,CAN_TX复用推挽输出,CAN_ ...
- I.MX6 bootargs 内核参数设定
/******************************************************************** * I.MX6 bootargs 内核参数设定 * 说明: ...