本文作者:黄邦勇帅(原名:黄勇)
本文不会对C++语法进行讲解(只会对不常用语法进行分析讲解),本文假设读者已经很熟悉C++语法了,若读者对C++语法不熟悉,请参阅本人所编著C++语法系列专题文章。
本文是MFC编程最基础的内容,因此应对其熟练掌握,本文对MFC封装(包装)API程序的原理作了细致深入的讲解,讲解了源代码封装SDK程序的关键类“状态信息类”及其使用的原理,让读者明白MFC是怎样封装SDK程序的,接着本文对MFC的源码执行流程和关键源代码进行了详细的分析,对源码的讲解从源码中使用到的不常用的语法分析入手,以图文并茂的形式着重讲解了源码创建和注册窗口的流程及过程,让读者彻底明白源代码是怎样创建一个MFC窗口的,让读者明白应该怎样编写一个MFC程序,读完本文,读者会对MFC 有一个完全的框架认识,会让MFC变得不再那么复杂。
本文内容由浅入深,内容较为全面,本文使用VS2015和VS2005编译器进行讲解。
本文内容完全属于个人见解与参考文现的作者无关,限于水平有限,其中难免有误解之处,望指出更正。
声明:禁止抄袭,复印,转载本文,本文作者拥有完全版权。
主要参考文献:
1、windows程序设计(第5版珍藏版) [美]Charles Petzold著方敏张胜梁路平赵勇等译清华大学出版
社2010年9月
2、Visual C++2013入门经典(第7版) [美] Ivor Horton著李周芳江凌译清华大学出版社2015年1月
3、VC++深入详解孙鑫余安萍编著电子工业出版社2006年6月
4、深入浅出MFC(第2版) 侯俊杰著华中科技大学出版社出版日期不祥
5、MFC windows程序设计(第2版) [美] Jeff Prosise著北京博彥科技发展有限责任公司译清华大学出版社2007年5月
第2部分MFC的前世今生
第1章MFC编程入门
以下把使用windows API编程称为SDK编程,相应的API函数称为SDK函数
一、创建MFC图形界面项目
1、VC++2015创建MFC项目的步骤:选择【文件\新建\项目...】,弹出【新建项目】对话框,
然后左侧选择“win32”,在右侧选择“win32项目”(注意:不要选择“win32控制台应用程序”),在下方输入项目的名称和选择项目保存的位置,点确定后弹出【win32应用程序向导】对话框,然后再在左侧选择“应用程序选项”,在右侧选中“windows应用程序”
和“空项目”,然后点击完成,创建项目成功。
2、注意:若在【新建项目】对话框中左侧选择“MFC”,然后在右侧选择“MFC应用程序”,
是使用MFC向导创建MFC图形界面,使用向导创建图形界面程序员不需任何编程,只需直接运行就能得到一个由系统自动创建的图形界面,此时已由系统默认编写好了必要的
程序,此种形式创建的MFC图形界面对于理解MFC图形界面的原理有一定难度。
3、创建好空项目后,在左侧的【解决方案管理器】的【源文件】上右击鼠标,选择【添加\
新建项...】,弹出【添加新项】对话框,然后在左侧选择“代码”,然后在右侧选择“C++文件(.cpp)”,然后在对话框下面输入文件名,点击确定,然后在解决方案管理器中到该cpp文件,并双击该文件,现在就可以在该文件中编写程序代码了。
二、设置MFC图形界面
1、以下设置都是以空的win32项目为基础讲解的,并假设项目的名称为XXX。
2、设置使用MFC编程还是使用windows API编程(即SDK编程)。
选择【项目\XXX属性...】,弹出【XXX属性页】对话框,然后在对话框的左侧选择【配置属性\常规】,在对话框右侧到【MFC的使用】,若该项设置为【使用标准的windows 库】则表示使用windows API进行编程。要使用MFC编程应把该项设置为【在静态库中使用MFC】或【在共享DLL中使用MFC】,本书使用【在共享DLL中使用MFC】进行讲解。
3、设置字符的编码集与_T宏
选择【项目\XXX属性...】,弹出【XXX属性页】对话框,然后在对话框的左侧选择【配置属性\常规】,在对话框右侧到【字符集】,其默认值为使用Unicode字符集,即宽字符集,若使用Unicode字符集进行编程,则应在字符串的双引号前面加上大写字母L,比如L”AAA”,否则编译器无法识别字符串。若使用其余字符集编程则不能在字符串的双引号前面加上大写字母L。编程时最好使用tchar.h中的_T宏来解决宽字符的问题。
4、在源文件中输入以下程序,并运行(按F5可运行程序)
示例2.1:一个简单的MFC示例程序(注意:请使用“在共享的DLL中使用MFC”运行程序) #include<afxw
in.h> //编写MFC程序,必须包含此头文件
class A:public CWinApp{public: //应用程序类CWinApp,一个程序只能有一个CWinApp的子类。
BOOL InitInstance(); }; //此函数必须被重新定义。
class B:public CFrameWnd{public: //窗口类CFrameWnd
B(){Create(NULL,_T("HYONG"),WS_OVERLAPPEDWINDOW);}}; //使用构造函数创建窗口BOOL A::InitInstance(){ //窗口必须经过此成员函数进行创建
m_pMainWnd=new B(); /*通过类B的构造函数创建窗口,此处必须是new创建的对象,而不能是B mb;
m_pMainWnd=&mb;因为这样会使m_pMainWnd保存一个局部变量的地址,而发生意想
不到的错误。且在释放内存时,还可能会产生内存泄漏*/
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;}
A ma; //必须创建类A类型的对象,且只能创建一个。
//A ma1; //错误,只能创建一个类A类型的对象。
三、MFC程序的特点
1、MFC程序的显著特点是呈现在程序员面前的代码没有入口函数main或Winmain,当然,
这并不表示MFC没有入口函数,只是入口函数被系统隐藏在了系统源文件中,在运行程序时会把这些源文件链接在一起,因此程序员所编写的程序都是在全局范围内的,只是程
序员所编写的这个源文件没有入口函数而已。
2、MFC把winAPI拆分成了大约如下几个部分:窗口注册、窗口创建、窗口显示/更新、消
息处理、入口函数(即main函数)。其中窗口注册、窗口创建、窗口显示/更新等与窗口有关的函数使用一些类进行封装,把这些类统称为窗口类(比如示例中的CFrameWnd类);、把与窗口无关的其他函数(比如消息处理等)使用另一些类进行封装,把这些类统称为应用程序类(比如示例中的CWinApp类);而入口函数的作用除了是程序的入口外,还把这两种类型的对象相互联系了起来。
3、MFC把几乎所有的功能都封装(包装)在了类之中成为类的成员函数,这些成员函数的名
称甚至与SDK函数的名称相同。因此使用MFC编程的基本思想就是创建类对象,调用类中的成员函数进行编程,这与SDK编程调用SDK函数不相同。
4、MFC的运行原理与SDK程序是相同的,即都要经过窗口类注册、窗口创建、消息循环、
消息处理等环节,只是MFC把这些环节全都封装在了类之中,从而使程序变得更简洁,但也更不那么清晰明了了。
5、在MFC中也可以调用SDK函数,其方法是使用作用域解析运算符”::”进行调用。
6、Afx函数:以Afx开头的函数都是全局函数(这一点要牢记)。
7、注意:使用MFC编程,应把【项目\xxx属性...】,“配置属性\常规”,右侧的“MFC的使
用”设置为“在共享DLL中使用MFC”
四、查看源文件的内容及MSDN
1、注意:为了弄清程序代码的原理,在程序中读者应经常在标识符上右击鼠标,选择“转到
定义”,查看该标识符的定义。
2、MFC中的类(包含隐藏的WinMain函数)都被定义在系统的安装目录中:
vx2005路径为:….\Microsoft Visual Studio 8\VC\atlmfc\src\mfc
….\Microsoft Visual Studio 8\VC\atlmfc\include
vs2017 路径为:
…\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.11.25503\atlmfc\src\mfc
….\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.11.25503\atlmfc\include 具体视你的安装目录而定,这些文件夹中的文件是编译器源文件或头文件,可使用VC++进行查看,也可使用记事本查看其源代码。注意:不要修改这些源文件的内容,否则编译器可能会崩溃。
3、源文件的代码特别长(长的有数千行之多),因此要在源代码中查某个名称时,请使用“查
(ctrl+f)”功能,在VC++中到名称后把鼠标停留在该名称上一会儿,会弹出一个小黄框,显示该名称属于哪一个类,在VC++编辑窗口的上方也可看到名称所属的类,形式如下图
4、要读懂源码需要有较强的C++语法知识,而且源码中有大量的宏,再加上源码标识符的
名称特别的长,这给阅读源码带来一定难度,下面会介绍源码中一些常见的宏和重要的C++语法。
5、学习MFC还需要结合MSDN进行学行,MSDN就是VC++的帮助文档,MSDN需要单
独安装,也可在微软上查看(若未安装MSDN,则在VC++编译器中直接按F1就能进
深入浅出mfc入微软),使用MSDN需使用查功能,在MSDN中查看类继承结构的方法为,在查中查任一MFC类(比如CWnd类),在最左下角到链接“Hierarchy Char(层次结构图)”,即可看到MFC所有类的组织结构图。
五、读懂源文件(源文件中常用的语法、宏、函数)
1、memset函数简介(此函数会经常使用,应记住其用法)
原型为:void* memset(void* p, int c, size_t n);
1)、意义:把p所指开始的n个字节内存设置为值c,并反回p。
2)、memset函数用来初始化一段内存空间,或者说把一段内存空间设置为某个值。
3)、memset函数常被用来快速设置结构体中各个成员为某个值(通常为0),比如struct S{int
a;int b;char c;}; S ms; 则memset(&ms, 0, sizeof(ms));表示把结构体类型S的对象ms 的所有成员的值设置为0。
2、C++语法:this指针与虚函数(重要语法)
class A{public:
virtual void f(){cout<<"AF"<<endl;}
void g(){f();} }; // g中对f的调用等效于this->f();
class B:public A{public: void f(){cout<<"BF"<<endl;} };
void main(){ B mb;
mb.g();} //输出BF,调用的是类B中的f函数。
1)、调用mb.g()传递给g函数的this指针是类B类型的对象mb的地址,即相当于在g函
数内部this = &mb;因此在g中对f的调用,将会因为this指向的是类A的子类对象mb的地址,而调用子类B中被重定义的f函数,从而调用mb.g()输出BF。
2)、产生以上结果的原因分析
①、需要的基础知识:需要知道C++语法对象模型原理中的“名称改编”规则,此处
没必要深入研究,以下内部转换规则是一个大致的规则(因为具体是很复杂的,各
编译器转换后使用的名字也不一致,但原理是一样的)。
②、类A的成员函数g会被编译器内部转换为:void g(const A* this){A *pa=this;}
③、调用mb.g()会被编译器内部转换为:g(&mb);
④、由以上可看到,类A中的成员函数g的this指针的类型是const A*,但mb.g()调
用后,经过实参的赋值其指向的类型是B*,即相当于语句const A* this=&mb;
⑤、对于父类构造函数中的this指针,会因为在构造父类对象之前,子类对象还未构
造完成,而使this指针只会调用父类中的虚函数,但是要注意,this指针仍是指
向的传进进来的(子类或父类)对象的地址的。比如
class A;
A *pa;
class A{public:
virtual void f(){cout<<"AF"<<endl;}
A(){ pa=this; //使用this初始化全局指针pa
f();}}; // 对f的调用等效于this->f();
class B:public A{public: void f(){cout<<"BF"<<endl;} };
void main(){ B mb; /*输出AF,调用的是类A中的f函数,因为此时类B对象的子类
部分还未构造完成。*/
pa->f(); } //输出BF ,因为此时类B 对象的子类部分已经构造完成了。
3、常使用的宏常量
1)、__FILE__宏:类型为char 字符串,表示文件的名称(含路径)。
2)、__LINE__宏:类型为整型,表示当前位于哪一行。
3)、_DA TE__宏: 类型为char 字符串,表示当前编译的日期。格式为:月 日 年
4)、__TIME__宏: 类型为char 字符串,表示当前编译的时间,格式为:时:分:秒。
4、TRACE 宏
1)、TRACE 宏类似于C 语言中的printf 函数,其使用方法也与其相同,比如
TRACE(“%d\n”,i);表示输出变量i 的值并换行,再如TRACE(“AAA”);输出字符串AAA 。
2)、TRACE 宏可用于在程序中作一些字符的输出,以检测程序的正确性,注意:TRACE
宏输出的信息显示在VC++编译器的“输出”窗口中。如下图所示
3)、TRACE 宏只在调试模式下有作用,在RELEAsE 模式下TRACE 宏不起任何作用。
4)、源码追踪(鼠标右键--—转到定义):
#define TRACE ATLTRACE
#define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
①、CTraceFileAndLineInfo 是类A TL 的嵌套类,其构造函数含有两个形参,而且
CTraceFileAndLineInfo 类对运算符”()”进行了多个版本的重载,因此也可以这样使
用TRACE 宏A TL::CTraceFileAndLineInfo(__FILE__,__LINE__)(“aaa”); 输出字符
串aaa 。
②、CTraceFileAndLineInfo 类重载的()函数的其中一个版本为
void operator ()(DWORD_PTR dwCategory, UINT nLevel, const char *pszFmt, ...)
其前两个参数都是整型,因此在WinMain 中的调用TRACE(traceAppMsg, 0, "Warning:
Destroying non-NULL m_pMainWnd\n");使用的是该版本。
③、A TL::CTraceFileAndLineInfo 类的源代码此处不作分析,在本文只需知道TRACE
宏怎么使用即可。
5、ASSERT 宏
1)、ASSERT 宏类似于C++中的断言assert ,在C++使用assert 需包含assert.h 头文件。
2)、断言类似于if 语句,即ASSERT(exp);若exp 的表达式为真,则执行其后的语句,否
则程序弹出一个包含三个选项“终止、重试、忽略”的对话框,各选项意义如下: ①、若选择“终止”则结束程序,
②、若选择“忽略”则程序忽略该断言继续执行其后的语句,
③、若选择“重试”,则产生一个断点,并再次弹出一个对话框,若选择“中断”则程
序跳至产生断点的地方,若选择“继续”,则程序继续执行。
3)、ASSERT 宏只在调试模式下有作用,在RELEASE 模式下ASSERT 宏不起任何作用。
4)、源码追踪(鼠标右键--—转到定义):
#define ASSERT(f) DEBUG_ONLY((void
) ((f) ||\ TRACE 宏显示于输出窗口 TRACE 宏输出的信息
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论