原文地址:

http://www.jizhuomi.com/software/267.html

很多做软件开发的人都有一种对事情刨根问底的精神,例如我们一直在用的MFC,很方便,不用学太多原理性的知识就可以做出各种窗口程序,但喜欢钻研的朋友肯定想知道,到底微软帮我们做了些什么,让我们在它的框架下可以简单的写程序。本文开始就跟大家分享一位同行前辈写的MFC核心机制分析(稍作整理),语言朴实易懂,在读完此深入浅析的剖析系列后,相信留给大家的是对MFC运行机制的深入理解。

MFC六大核心机制概述

我们选择了C++,主要是因为它够艺术、够自由,使用它我们可以实现各种想法,而MFC将多种可灵活使用的功能封装起来,我们岂能忍受这种“黑盒”操作?于是研究分析MFC的核心机制成为必然。

首先,列出要讲的MFC六大核心机制:

1、MFC程序的初始化。
       2、运行时类型识别(RTTI)。
       3、动态创建。
       4、永久保存。
       5、消息映射。
       6、消息传递。

本文讲第一部分,MFC程序的初始化过程。

简单的MFC窗口程序

设计一个简单完整MFC程序,产生一个窗口。当然这不能让AppWizard自动为我们生成。我们可以在Win32 Application工程下面那样写:

C++代码
  1. #include <afxwin.h>
  2. class MyApp : public CWinApp
  3. {
  4. public:
  5. BOOL InitInstance()  //②程序入点
  6. {
  7. CFrameWnd *Frame=new CFrameWnd();//构造框架
  8. m_pMainWnd=Frame; //将m_pMainWnd设定为Frame;
  9. Frame->Create(NULL,"最简单的窗口");//建立框架
  10. Frame->ShowWindow(SW_SHOW);  //显示框架
  11. return true;         //返回
  12. }
  13. };
  14. MyApp theApp;  //①建立应用程序。

设定链接MFC库,运行,即可看见一个窗口。

从上面,大家可以看到建立一个MFC窗口很容易,只用两步:一是从CWinApp派生一个应用程序类(这里是MyApp),然后建立应用程序对象(theApp),就可以产生一个自己需要的窗口(即需要什么样就在InitInstance()里创建就行了)。

整个程序,就改写一个InitInstance()函数,创建那么一个对象(theApp),就是一个完整的窗口程序。这就是“黑盒”操作的魔力!

在我们正想为微软鼓掌的时候,我们突然觉得心里空荡荡的,我们想知道微软帮我们做了什么事情,而我们想编自己的程序时又需要做什么事情,哪怕在上面几行的程序里面,我们还有不清楚的地方,比如,干嘛有一个m_pMainWnd指针变量,它从哪里来,又要到哪里去呢?想一想在DOS下编程是多么美妙的一件事呵,我们需要什么变量,就声明什么变量,需要什么样的函数,就编写什么样的函数,或者引用函数库……但是现在我们怎么办?

我们可以逆向思维一下,MFC要达到这种效果,它是怎么做的呢?首先我们要弄明白,VC++不是一种语言,它就象我们学c语言的时候的一个类似记事本的编辑器(请原谅我的不贴切的比喻),所以,在VC里面我们用的是C++语言编程,C++才是根本(初学者总是以为VC是一门什么新的什么语言,一门比C++先进很多的复杂语言,汗)。说了那么多,我想用一句简单的话概括“MFC黑箱’,就是为我们的程序加入一些固化的‘C++代码’的东西”。

既然MFC黑箱帮我们加入了代码,那么大家想想它会帮我们加入什么样的代码呢?他会帮我们加入求解一元二次方程的代码吗?当然不会,所以它加入的实际上是每次编写窗口程序必须的,通用的代码。

再往下想,什么才是通用的呢?我们每次视窗编程都要写WinMain()函数,都要有注册窗口,产生窗口,消息循环,回调函数……即然每次都要的东西,就让它们从我们眼前消失,让MFC帮忙写入!

手动模拟MFC程序的初始化

要知道MFC初始化过程,大家当然可以跟踪执行程序。但这种跟踪很麻烦,我相信大家都会跟踪的晕头转向。本人觉得哪怕你理解了MFC代码,也很容易让人找不着北,我们完全不懂的时候,在成千上万行程序的迷宫中如何能找到出口?

我们要换一种方法,不如就来重新编写个MFC库吧,哗!大家不要笑,小心你的大牙,我不是疯子(虽然疯子也说自己不疯)。我们要写的就是最简单的MFC类库,就是把MFC宏观上的,理论上的东西写出来。我们要用最简化的代码,简化到刚好能运行。

1、需要“重写”的MFC库

既然,我们这一节写的是MFC程序的初始化过程,上面我们还有了一个可执行的MFC程序。程序中只是用了两个MFC类,一个是CWinApp,另一个是CFrameWnd。当然,还有很多同样重要MFC类如视图类,文档类等等。但在上面的程序可以不用到,所以暂时省去了它(总之是为了简单)。

好,现在开始写MFC类库吧……唉,面前又有一个大难题,就是让大家背一下MFC层次结构图。天,那张鱼网怎么记得住,但既然我们要理解他,总得知道它是从那里派生出来的吧。

考虑到大家都很辛苦,那我们看一下上面两个类的父子关系(箭头代表派生):

CObject->CCmdTarget->CWinThread->CWinApp->自己的重写了InitInstance()的应用程序类。
       CObject(同上)->CCmdTarget(同上)->CWnd->CFrameWnd

看到层次关系图之后,终于可以开始写MFC类库了。按照上面层次结构,我们可以写以下六个类(为了直观,省去了构造函数和析构函数)。

C++代码
  1. /
  2. class CObiect{};//MFC类的基类。
  3. class CCmdTarget : public CObject{};
  4. ------------------------------------------------
  5. class CWinThread : public CCmdTarget{};
  6. class CWinApp : public CWinThread{};
  7. ------------------------------------------------
  8. class CWnd : public CCmdTarget{};
  9. class CFrameWnd : public CWnd{};
  10. /

大家再想一下,在上面的类里面,应该有什么?大家马上会想到,CWinApp类或者它的基类CCmdTarget里面应该有一个虚函数virtual BOOL InitInstance(),是的,因为那里是程序的入口点,初始化程序的地方,那自然少不了的。可能有些朋友会说,反正InitInstance()在派生类中一定要重载,我不在CCmdTarget或CWinApp类里定义,留待CWinApp的派生类去增加这个函数可不可以。扯到这个问题可能有点越说越远,但我想信C++的朋友对虚函数应该是没有太多的问题的。总的来说,作为程序员如果清楚知道基类的某个函数要被派生类用到,那定义为虚函数要方便很多。

也有很多朋友问,C++为什么不自动把基类的所有函数定义为虚函数呢,这样可以省了很多麻烦,这样所有函数都遵照派生类有定义的函数就调用派生类的,没定义的就调用基类的,不用写virtual的麻烦多好!其实,很多面向对象的语言都这样做了。但定义一个虚函数要生成一个虚函数表,要占用系统空间,虚函数越多,表就越大,有时得不偿失!这里哆嗦几句,是因为往后要说明的消息映射中大家更加会体验到这一点,好了,就此打往。

上面我们自己解决了一个问题,就是在CCmdTarge写一个virtual BOOL InitInstance()。

2、WinMain()函数和CWinApp类

大家再往下想,我们还要我们MFC“隐藏”更多的东西:WinMain()函数,设计窗口类,窗口注册,消息循环,回调函数……我们马上想到封装想封装他们。大家似乎隐约地感觉到封装WinMain()不容易,觉得WinMain()是一个特殊的函数,许多时候它代表了一个程序的起始和终结。所以在以前写程序的时候,我们写程序习惯从WinMain()的左大括写起,到右大括弧返回、结束程序。

我们换一个角度去想,有什么东西可以拿到WinMain()外面去做,许多初学者们,总觉得WinMain()函数是天大的函数,什么函数都好象要在它里面才能真正运行。其实这样了解很片面,甚至错误。我们可以写一个这样的C++程序:

C++代码
  1. #include <iostream.h>
  2. class test{
  3. public:
  4. test(){cout<<"请改变你对main()函数的看法!"<<endl;}
  5. };
  6. test test1;
  7. /**************************/
  8. void main(){}

在上面的程序里,入口的main()函数表面上什么也不做,但程序执行了(注:实际入口函数做了一些我们可以不了解的事情),并输出了一句话(注:全局对象比main()首先运行)。现在大家可以知道我们的WinMain()函数可以什么都不做,程序依然可以运行,但没有这个入口函数程序会报错。

那么WinMain()函数会放哪个类上面呢,请看下面程序:

C++代码
  1. #include <afxwin.h>
  2. class MyApp : public CWinApp
  3. {
  4. public:
  5. BOOL InitInstance()  //②程序入点
  6. {
  7. AfxMessageBox("程序依然可以运行!");
  8. return true;
  9. }
  10. };
  11. MyApp theApp;  //①建立应用程序。

大家可以看到,我并没有构造框架,而程序却可以运行了——弹出一个对话框(如果没有WinMain()函数程序会报错)。上面我这样写还是为了直观起见,其实我们只要写两行程序:

#include <afxwin.h>
       CWinApp theApp;     //整个程序只构造一个CWinApp类对象,程序就可以运行!

所以说,只要我们构造了CWinApp对象,就可以执行WinMain()函数。我们马上相信WinMain()函数是在CWinApp类或它的基类中,而不是在其他类中。其实这种看法是错误的,我们知道编写C++程序的时候,不可能让你在一个类中包含入口函数,WinMain()是由系统调用,跟我们的平时程序自身调用的函数有着本质的区别。我们可以暂时简单想象成,当CWinApp对象构造完的时候,WinMain()跟着执行。

现在大家明白了,大部分的“通用代码(我们想封装隐藏的东西)”都可以放到CWinApp类中,那么它又是怎样运行起来的呢?为什么构造了CWinApp类对象就“自动”执行那么多东西。

大家再仔细想一下,CWinApp类对象构造之后,它会“自动”执行自己的构造函数。那么我们可以把想要“自动”执行的代码放到CWinApp类的构造函数中。

那么CWinApp类可能打算这样设计(先不计较正确与否):

C++代码
  1. class CWinApp : public CWinThead{
  2. public:
  3. virtual BOOL InitInstance(); //解释过的程序的入点
  4. CWinApp ::CWinApp(){   //构造函数
  5. WinMain();   //这个是大家一眼看出的错误
  6. Create();    //设计、创建、更新显示窗口
  7. Run();     //消息循环
  8. //
  9. }
  10. };

写完后,大家又马上感觉到似乎不对,WinMain()函数在这里好象真的一点用处都没有,并且能这样被调用吗(请允许我把手按在圣经上声明一下:WinMain()不是普通的函数,它要肩负着初始化应用程序,包括全局变量的初始化,是由系统而不是程序本身调用的,WinMain()返回之后,程序就结束了,进程撤消)。再看Create()函数,它能确定设计什么样的窗口,创建什么样的窗口吗?如果能在CWinApp的构造函数里确定的话,我们以后设计MFC程序时窗口就一个样,这样似乎不太合理。

回过头来,我们可以让WinMain()函数一条语句都不包含吗?不可以,我们看一下WinMain() 函数的四个参数:

WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

其中第一个参数指向一个实例句柄,我们在设计WNDCLASS的时候一定要指定实例句柄。我们窗口编程,肯定要设计窗口类。所以,WinMain()再简单也要这样写:

int WinMain(HINSTANCE hinst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
       {  hInstance=hinst }

既然实例句柄要等到程序开始执行才能知道,那么我们用于创建窗口的Create()函数也要在WinMain()内部才能执行(因为如果等到WinMain()执行完毕后,程序结束,进程撤消,当然Create()也不可能创建窗口)。

再看Run()(消息循环)函数,它能在WinMain()函数外面运行吗?众所周知,消息循环就是相同的那么几句代码,但我们也不要企图把它放在WinMain()函数之外执行。

所以我们的WinMain()函数可以像下面这样写:

WinMain(……)
       {
              ……窗口类对象执行创建窗口函数……
              ……程序类对象执行消息循环函数……
       }

对于WinMain()的问题,得总结一下,我们封装的时候是不可以把它封装到CWinApp类里面,但由于WinMain()的不变性(或者说有规律可循),MFC完全有能力在我们构造CWinApp类对象的时候,帮我们完成那几行代码。

转了一个大圈,我们仿佛又回到了SDK编程的开始。但现在我们现在能清楚地知道,表面上MFC与SDK编程截然不同,但实质上MFC只是用类的形式封装了SDK函数,封装之后,我们在WinMain()函数中只需要几行代码,就可以完成一个窗口程序。我们也由此知道了应如何去封装应用程序类(CWinApp)和主框架窗口类(CFrameWnd)。下面把上开始设计这两个类。

3、MFC库的“重写”

为了简单起见,我们忽略这两个类的基类和派生类的编写,可能大家会认为这是一种很不负责任的做法,但本人觉得这既可减轻负担,又免了大家在各类之间穿来穿去,更好理解一些(我们在关键的地方作注明)。还有,我把全部代码写在同一个文件中,让大家看起来不用那么吃力,但这是最不提倡的写代码方法,大家不要学哦!

C++代码
  1. #include <windows.h>
  2. HINSTANCE hInstance;
  3. class CFrameWnd
  4. {
  5. HWND hwnd;
  6. public:
  7. CFrameWnd();   //也可以在这里调用Create()
  8. virtual ~CFrameWnd();
  9. int Create();    //类就留意这一个函数就行了!
  10. BOOL ShowWnd();
  11. };
  12. class CWinApp1
  13. {
  14. public:
  15. CFrameWnd* m_pMainWnd;//在真正的MFC里面
  16. //它是CWnd指针,但这里由于不写CWnd类
  17. //只要把它写成CFrameWnd指针
  18. CWinApp1* m_pCurrentWinApp;//指向应用程序对象本身
  19. CWinApp1();
  20. virtual ~CWinApp1();
  21. virtual BOOL InitInstance();//MFC原本是必须重载的函数,最重要的函数!!!!
  22. virtual BOOL Run();//消息循环
  23. };
  24. CFrameWnd::CFrameWnd(){}
  25. CFrameWnd::~CFrameWnd(){}
  26. int CFrameWnd::Create()   //封装创建窗口代码
  27. {
  28. WNDCLASS wndcls;
  29. wndcls.style=0;
  30. wndcls.cbClsExtra=0;
  31. wndcls.cbWndExtra=0;
  32. wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
  33. wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
  34. wndcls.hIcon=LoadIcon(NULL,IDC_ARROW);
  35. wndcls.hInstance=hInstance;
  36. wndcls.lpfnWndProc=DefWindowProc;//默认窗口过程函数。
  37. //大家可以想象成MFC通用的窗口过程。
  38. wndcls.lpszClassName="窗口类名";
  39. wndcls.lpszMenuName=NULL;
  40. RegisterClass(&wndcls);
  41. hwnd=CreateWindow("窗口类名","窗口实例标题名",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);
  42. return 0;
  43. }
  44. BOOL CFrameWnd::ShowWnd()//显示更新窗口
  45. {
  46. ShowWindow(hwnd,SW_SHOWNORMAL);
  47. UpdateWindow(hwnd);
  48. return 0;
  49. }
  50. /
  51. CWinApp1::CWinApp1()
  52. {
  53. m_pCurrentWinApp=this;
  54. }
  55. CWinApp1::~CWinApp1(){}
  56. //以下为InitInstance()函数,MFC中要为CWinApp的派生类改写,
  57. //这里为了方便理解,把它放在CWinApp类里面完成!
  58. //你只要记住真正的MFC在派生类改写此函数就行了。
  59. BOOL CWinApp1::InitInstance()
  60. {
  61. m_pMainWnd=new CFrameWnd;
  62. m_pMainWnd->Create();
  63. m_pMainWnd->ShowWnd();
  64. return 0;
  65. }
  66. BOOL CWinApp1::Run()//封装消息循环
  67. {
  68. MSG msg;
  69. while(GetMessage(&msg,NULL,0,0))
  70. {
  71. TranslateMessage(&msg);
  72. DispatchMessage(&msg);
  73. }
  74. return 0;
  75. } //封装消息循环
  76. CWinApp1 theApp;   //应用程序对象(全局)
  77. int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hPrevInstance,   LPSTR lpCmdLine,  int nCmdShow)
  78. {
  79. hInstance=hinst;
  80. CWinApp1* pApp=theApp.m_pCurrentWinApp;
  81. //真正的MFC要写一个全局函数AfxGetApp,以获取CWinApp指针。
  82. pApp->InitInstance();
  83. pApp->Run();
  84. return 0;
  85. }

代码那么长,实际上只是写了三个函数,一是CFrameWnd类的Create(),第二个是CWinApp类的InitInstance()和Run()。在此特别要说明的是InitInstance(),真正的MFC中,那是我们跟据自己构造窗口的需要,自己改写这个函数。

大家可以看到,封装了上面两个类以后,在入口函数WinMain中就写几行代码,就可以产生一个窗口程序。在MFC中,因为WinMain函数就是固定的那么几行代码,所以MFC绝对可以帮我们自动完成(MFC的特长就是帮我们完成有规律的代码),也因此我们创建MFC应用程序的时候,看不到WinMain函数。

分享源自:http://blog.csdn.net/liyi268/article/details/297875

MFC六大核心机制之一MFC程序的初始化相关推荐

  1. MFC六大核心机制之一:MFC程序的初始化

    很多做软件开发的人都有一种对事情刨根问底的精神,例如我们一直在用的MFC,很方便,不用学太多原理性的知识就可以做出各种窗口程序,但喜欢钻研的朋友肯定想知道,到底微软帮我们做了些什么,让我们在它的框架下 ...

  2. 如何优雅的写UI——(1)MFC六大核心机制-程序初始化

    很多做软件开发的人都有一种对事情刨根问底的精神,例如我们一直在用的MFC,很方便,不用学太多原理性的知识就可以做出各种窗口程序,但喜欢钻研的朋友肯定想知道,到底微软帮我们做了些什么,让我们在它的框架下 ...

  3. MFC六大核心机制之二:运行时类型识别(RTTI)

    上一节讲的是MFC六大核心机制之一:MFC程序的初始化,本节继续讲解MFC六大核心机制之二:运行时类型识别(RTTI). typeid运算子 运行时类型识别(RTTI)即是程序执行过程中知道某个对象属 ...

  4. MFC六大核心机制之五、六:消息映射和命令传递

    作为C++程序员,我们总是希望自己程序的所有代码都是自己写出来的,如果使用了其他的一些库,也总是千方百计想弄清楚其中的类和函数的原理,否则就会感觉不踏实.所以,我们对于在进行MFC视窗程序设计时经常要 ...

  5. MFC六大核心机制之三:动态创建

    MFC中很多地方都使用了动态创建技术.动态创建就是在程序运行时创建指定类的对象.例如MFC的单文档程序中,文档模板类的对象就动态创建了框架窗口对象.文档对象和视图对象.动态创建技术对于希望了解MFC底 ...

  6. MFC六大核心机制之四:永久保存(串行化)

    永久保存(串行化)是MFC的重要内容,可以用一句简明直白的话来形容其重要性:弄懂它以后,你就越来越像个程序员了! 如果我们的程序不需要永久保存,那几乎可以肯定是一个小玩儿.那怕我们的记事本.画图等小程 ...

  7. MFC消息响应机制及映射机制理解

    一.MFC消息响应机制分析 ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了 ...

  8. 微软MFC技术运行机制

    我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下微软MFC技术运行机制. 很多初学者误以为VC++开发必须使用MFC,其实不一定的.MFC的使用只能是提高程序在某些情况下的开发效率,而不能 ...

  9. MFC六大关键技术之初始化过程

    MFC六大关键技术之初始化过程 我并不认为MFC减轻了程序员们的负担,MFC出现的目的虽然似乎是为了让程序员不用懂得太多就可以进行视窗编程,但本人在MFC里徘徊了很久很久(因为那时没有书本详细介绍MF ...

最新文章

  1. 设计模式的理解:构造器模式(Builder Pattern)
  2. 阿里云低代码行业智能开放平台开拓行业AI应用新方法
  3. 织梦模板不支持html,html中{}是什么样的模板?如何使用它?HTML5template模板标签是什么?html常用的字体样式是什么?dede后台文件为什么不能生成html?...
  4. 修改软件服务器json返回数据格式,AngularJS处理服务器端返回的JSON数据的格式问题...
  5. 算法竞赛入门经典 例题6-2 铁轨(C、python)
  6. Linux挂载点和文件系统类型介绍
  7. 随想录(一种新的读写锁的写法)
  8. springboot前台页面写Java代码,接收后台数据,SpringBoot整合Thymeleaf的使用
  9. activiti 动态加载任务执行人(基于jeesit)
  10. 十分钟django后台 simpleui -含自定义后台首页
  11. Excel数据透视表数据源自动更新方法
  12. python 小说爬虫_初次尝试python爬虫,爬取小说网站的小说。
  13. 《弃子长安》第三章 月下魅影
  14. 7-7 阿泽的交友标准 (10 分)
  15. python0基础 第三节
  16. 聊天室页面问题和解决方案
  17. #2708. 黑暗(dark)
  18. WEB基础与前端开发--课程表页面的设计
  19. 童年记忆中的各种水果
  20. web实现视频播放-服务端所需工作

热门文章

  1. 项目管理相关参数说明计算(PV、AC、EV、BAC、EAC、ETC等)
  2. 电子邮件的POP3/SMTP/IMAP服务
  3. Gesture Recognition Dataset: Jester 数据集解压
  4. HTML(十二)三种常见布局:三栏式布局 双飞翼布局 圣杯布局
  5. python副业推荐以及变现渠道介绍,接单注意事项,超详细
  6. 03-Docker-配置用户组及加速器
  7. 可视频通话的软电话eyeBeam下载地址
  8. Go报错Finished running tool: 路径,current directory outside main module .... dependencies 的解决方法
  9. Visual Studio 2019安装AutoCAD_2020_dotnet_wizards
  10. CSDN日报20170322——《关于软件研发的一些体会总结》