[普通]MFC框架程序剖析

作者(passion) 阅读(913次) 评论(0) 分类( c++)

学了一段Win32SDK应用程序以后,因为种种杂七杂八的事情,让windows程序设计的内容停滞了很长一段时间。但是我今天还是鼓足了勇气,继续开始后面的内容。(不过后面的笔记不再是跟着杨力祥老师的上课内容了,因为他对MFC的讲解似乎课程剩下的不是很足,所以我换了孙鑫老师的听)。
咱们直接从第三节课讲起吧。第一节课讲的是用Win32SDK应用程序写“hello world”,我们之前已经做过很多遍了,这里只简要的回顾一下整个程序的脉络:
WinMain函数是整个程序的入口,相当于C语言中的main函数,在WinMain中,我们完成了一下几个重要的事情:设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,进入消息循环。在设置窗口类中,指明消息处理函数,并把这个重要的信息在注册窗口时告诉操作系统,而在消息循环,每当我的应用程序收到了消息,都会把这个消息投递到这个应用程序的消息队列中,然后程序依次从中取走消息,并把消息告诉操作系统,操作系统调用消息处理函数来响应这些消息。
首先,个人觉得windows应用程序跟dos下的控制台应用程序最大的区别有两点。第一点是表象的:控制台应用程序是“黑屏的”,而windows应用程序是基于“窗口的”;第二点是内在的,控制台应用程序的核心内容是与操作系统无关的(虽然我们总会频繁的使用printf是得能从屏幕上显示打印结果);而windows应用程序是跟操作系统密切相关的。
第二讲主要是复习C++里面的一些基本知识:类、继承、派生、多态、重载等等。这里就不提了。
下面我们言归正传,开始MFC的学习。MFC是微软基础类库,它是对我们前面使用的windowsAPI函数,使用面向对象的方法进行了封装,它大大的简化了应用程序的开发过程。(顺便吐槽几句,这个封装其实做的并不是非常出色,使用了大量的宏,而C++程序员是非常讨厌宏的,但是总而言之,用起来还不错,尤其是使用AppWizard开发向导以后)。
这节课的主要目的,就是在于向读者展示:尽管MFC对windowsAPI进行了封装,但是它的程序在执行的过程中,总是要遵循API里面设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,进入消息循环的步骤。
首先,我们新建一个单文档的MFC应用程序,对于其他内容保持默认设定,然后我们不写一行代码,直接编译程序,然后执行,就能看到一个像模像样的文档程序了。这当然是AppWizard的功劳,它默默的为你写了5个类:假设的工程名为CH_3_TEST,那么这5个类分别是:CAboutDlg、CCH_3TESTApp、CCH_3TESTDoc、CCH_3TESTView和CMainFrame,还有一个全局变量theApp。注意在编写MFC的程序时,我们更多的使用“类视图”而不是文件视图。
在这么多的代码中,你无法搜索到Win32SDK应用程序下的WinMain,更找不到我们熟悉的RegisterClass,CreateWindow,ShowWindow,UpdateWindow,以及GetMessage等等。那么写代码的入口在哪里,又是如何执行的呢?
找不到的原因在于,MFC把它们都封装了起来。它们对应的源文件在VC++6.0下面的安装目录:\Microsoft Visual Studio\VC98\MFC\SRC下面,我们粗粗把这些函数一一还原。
在APPMODUL.CPP下面找到:_tWinMain,而_tWinMain是一个宏,它其实就是WinMain。如果不信,可以在_tWinMain处设一个断点,我们就会发现,调试运行时程序会执行到断点处。
那么下面的问题是?这5个类是如何与WinMain关联到一起的?答案是通过全局变量theApp。我们可以在CCH_3TESTApp的构造函数处设一个断点,我们会发现整个程序会先运行构造函数,在运行WinMain。这只有一种可能,就是因为整个程序中有一个CCH_3TESTApp类的全局对象,也就是theApp。这也可以通过加上断点加以验证。我们记得,对于Win32SDK应用程序,是通过一个句柄hInstance来代表整个程序的,而对于MFC,则是通过这个全局对象代表的。对于这个全局对象,由于构造它的时候需要调用基类的构造函数,我们就把自己写的程序跟MFC库关联的起来:CCH_3_TESTApp继承自CWinApp,它的构造函数定义在APPCORE.CPP中,其中有两句:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this;
其中的this指针指向的是theApp。
至此,我们明白了下面3件事情:我们定义了全局对象theApp,而theApp需要使用它的基类的构造函数,当构造完theApp后,进入WinMain函数。
而WinMain函数的执行,是通过调用AfxWinMain函数来实现的,这个函数的定义在WINMAIN.CPP中。在MFC中,Afx开头的函数都是全局函数,它们在每个类中都可以方便的调用。里面有两句值得注意:
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
其中AfxGetThread是THRDCORE.CPP中定义的。而它是通过AfxGetApp实现的。而它是通过afxCurrentWinApp实现的。afxCurrentWinApp是一个宏,实际上调用的是AfxGetModuleState()->m_pCurrentWinApp,而在CWinApp的构造函数中,我们已经把m_pCurrentWinApp这个变量赋值为this指针。兜了这么大一个圈,这个其实这两个函数获得的是同一个指针,它们指向全局对象。
接下来,AfxWinMain通过了3个函数来完成了设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,进入消息循环,以及窗口过程函数:InitApplication、InitInstance和
Run。
其中InitApplication做的是mfc内部管理所调用的函数,InitInstance是一个虚函数,它会调用派生类中的InitInstance。这个函数中ShowWindow和UpdateWindow我们似曾相识,它们调用的就是SDK下的对应的函数。但是注册窗口、创建窗口的步骤是在哪里完成的呢?这里不管是原书还是视频都没有讲清楚,调用过程,只说了结果:
AfxEndDeferRegisterClass(WINCORE.CPP中)完成的是注册窗口的操作。它对于窗口设置了大量默认的样式,这里只需要判断是样式中的哪一种,选择即可。然后调用AfxRegisterClass来完成注册。而创建窗口的过程就更加复杂了,因为自己没有尝试跟踪过,所以不做叙述了。希望等懂一些之后再补上。
而Run函数则完成的是消息循环工作:先是一个for循环,再套了while循环,其中PumpMessage中调了GetMessage、TranslateMessage和DispatchMessage这个三个函数。


大体的脉络就是这样的,可见虽然MFC乍看上去跟API毫无关系,但是追本溯源,它只是对API的封装,最后,还是调用API函数实现的,windows并没有提供另外的一套库来专门供C++程序员使用。
那么使用MFC的方便之处在哪里呢?我们可以举一个例子:给文本框程序添加一个按钮。
首先,按钮肯定是在接受到WM_CREAT消息时添加上去的,所以应该在响应WM_CREAT消息的函数OnCreate下添加一个一个CButton对象,然后调用它的Create函数来初始化它。但仔细一想这样做是有问题的,如果定义了一个局部对象,那么当OnCreate调用完成之后就会发生析构,窗口就消失了,所以应该把按钮设定为成员变量。
还有一点值得注意,就是如果是在CMainFrame的OnCreate下创建按钮,如果使用默认的位置,则会覆盖到原来的菜单。我们有两种思路解决这个问题:1.修改创建的位置。2.在view类里面创建。但是view里面并没有OnCreate函数,怎么办呢,这时我们的AppWizard就派生用场了:对这类添加一个CButton成员,在类名上单击右键,选择Add windows message Handler...然后选择消息,并“填空”完成消息响应函数就行了。这里就不再罗嗦了。
从这里我们也可以看出,view类的范围是窗口的显示部分,而frame类要更大一些,包含了菜单等内容。

« 上一篇:wifi共享上网(至尊版wifi)
« 下一篇:drcom至尊版使用openwrt路由器拨号
在这里写下您精彩的评论
  • 微信

  • QQ

  • 支付宝

返回首页
返回首页 img
返回顶部~
返回顶部 img