mfc六大核心机制之一:mfc程序的初始化

鸡啄米  •  扫码分享
我是创始人李岩:很抱歉!给自己产品做个广告,点击进来看看。  

&&&&&& 很多做软件开发的人都有一种对事情刨根问底的精神,例如我们一直在用的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. //////////////////////////////////////////////////// &&
  2. #include&<iostream.h> &&
  3. class&test{ &&
  4. public: &&
  5. &test(){cout<<"请改变你对main()函数的看法!"<<endl;} &&
  6. }; &&
  7. test&test1; &&
  8. /**************************/&&
  9. void&main(){} &&
  10. ////////////////////////////////////////////////////&&

&&&&&& 在上面的程序里,入口的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. &&
  12. 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. &&&//////////////////////// &&
  6. &&&winmain();&&&//这个是大家一眼看出的错误 &&
  7. &&&create();&&&&//设计、创建、更新显示窗口 &&
  8. &&&run();&&&&&//消息循环 &&
  9. &&&////////////////////// &&
  10. } &&
  11. };&&

&&&&&&&写完后,大家又马上感觉到似乎不对,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. &&
  4. class&cframewnd&& &&
  5. { &&
  6. &hwnd&hwnd; &&
  7. public: &&
  8. &cframewnd();&&&//也可以在这里调用create() &&
  9. &virtual&~cframewnd(); &&
  10. &int&create();&&&&//类就留意这一个函数就行了! &&
  11. &bool&showwnd(); &&
  12. }; &&
  13. class&cwinapp1&& &&
  14. { &&
  15. public: &&
  16. &cframewnd*&m_pmainwnd;//在真正的mfc里面 &&
  17. //它是cwnd指针,但这里由于不写cwnd类 &&
  18. //只要把它写成cframewnd指针 &&
  19. &cwinapp1*&m_pcurrentwinapp;//指向应用程序对象本身 &&
  20. &cwinapp1(); &&
  21. &virtual&~cwinapp1(); &&
  22. &virtual&bool&initinstance();//mfc原本是必须重载的函数,最重要的函数!!!! &&
  23. &virtual&bool&run();//消息循环 &&
  24. }; &&
  25. cframewnd::cframewnd(){} &&
  26. cframewnd::~cframewnd(){} &&
  27. &&
  28. int&cframewnd::create()&&&//封装创建窗口代码 &&
  29. { &&
  30. &wndclass&wndcls; &&
  31. &wndcls.style=0; &&
  32. &wndcls.cbclsextra=0; &&
  33. &wndcls.cbwndextra=0; &&
  34. &wndcls.hbrbackground=(hbrush)getstockobject(white_brush); &&
  35. &wndcls.hcursor=loadcursor(null,idc_cross); &&
  36. &wndcls.hicon=loadicon(null,idc_arrow); &&
  37. &wndcls.hinstance=hinstance; &&
  38. &wndcls.lpfnwndproc=defwindowproc;//默认窗口过程函数。 &&
  39. //大家可以想象成mfc通用的窗口过程。 &&
  40. &wndcls.lpszclassname="窗口类名"; &&
  41. &wndcls.lpszmenuname=null; &&
  42. ®isterclass(&wndcls); &&
  43. &&
  44. &hwnd=createwindow("窗口类名","窗口实例标题名",ws_overlappedwindow,0,0,600,400,null,null,hinstance,null); &&
  45. &&return&0; &&
  46. } &&
  47. &&
  48. bool&cframewnd::showwnd()//显示更新窗口 &&
  49. { &&
  50. &showwindow(hwnd,sw_shownormal); &&
  51. &updatewindow(hwnd); &&
  52. &return&0; &&
  53. } &&
  54. &&
  55. ///////////// &&
  56. cwinapp1::cwinapp1() &&
  57. { &&
  58. &m_pcurrentwinapp=this; &&
  59. } &&
  60. cwinapp1::~cwinapp1(){} &&
  61. //以下为initinstance()函数,mfc中要为cwinapp的派生类改写, &&
  62. //这里为了方便理解,把它放在cwinapp类里面完成! &&
  63. //你只要记住真正的mfc在派生类改写此函数就行了。 &&
  64. bool&cwinapp1::initinstance() &&
  65. { &&
  66. &m_pmainwnd=new&cframewnd; &&
  67. &m_pmainwnd->create(); &&
  68. &m_pmainwnd->showwnd(); &&
  69. &return&0; &&
  70. } &&
  71. &&
  72. bool&cwinapp1::run()//////////////////////封装消息循环 &&
  73. { &&
  74. &msg&msg; &&
  75. &while(getmessage(&msg,null,0,0)) &&
  76. &{ &&
  77. &&translatemessage(&msg); &&
  78. &&dispatchmessage(&msg); &&
  79. &} &&
  80. &return&0; &&
  81. }&//////////////////////////////////////////////////////封装消息循环 &&
  82. &&
  83. cwinapp1&theapp;&&&//应用程序对象(全局) &&
  84. &&
  85. int&winapi&winmain(&hinstance&hinst,&hinstance&hprevinstance,&&&lpstr&lpcmdline,&&int&ncmdshow) &&
  86. { &&
  87. &hinstance=hinst; &&
  88. &cwinapp1*&papp=theapp.m_pcurrentwinapp; &&
  89. //真正的mfc要写一个全局函数afxgetapp,以获取cwinapp指针。 &&
  90. &papp->initinstance(); &&
  91. &papp->run(); &&
  92. &return&0; &&
  93. }&&

&&&&&& 代码那么长,实际上只是写了三个函数,一是cframewnd类的create(),第二个是cwinapp类的initinstance()和run()。在此特别要说明的是initinstance(),真正的mfc中,那是我们跟据自己构造窗口的需要,自己改写这个函数。

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

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

除非特别注明,鸡啄米文章均为原创
转载请标明本文地址:http://www.jizhuomi.com/software/267.html
2012-11-22 22:20:43
作者:鸡啄米 分类:软件开发 浏览: 评论:5

随意打赏

提交建议
微信扫一扫,分享给好友吧。