摘要:本文章主要以mfc程序的执行流程、执行顺序等执行过程的剖析做出的结论,下面一起来看看原文的具体介绍。
一、mfc介绍
微软基础类库(英语:microsoft foundation classes,简称mfc)是微软公司提供的一个类库(class libraries),以c++类的形式封装了windows api,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含大量windows句柄封装类和很多windows的内建控件和组件的封装类。
二、mfc程序执行过程剖析
1)我们知道在win32api程序当中,程序的入口为winmain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用程序的窗口函数。而在mfc程序当中我们不在能找到类似winmain这样的程序入口,取而代之的是一系列派生类的声明和定义以及一个冲cwinapp类派生而来的类的全局对象。cwinapp类被称之为应用程序对象,在一个mfc程序当中只允许有一个应用程序对象。由于cwinapp的派生对象是全局的,因此这个对象的构造函数会在所有的其他代码运行之前被调用,而由于cwinapp类当中包含了hwnd、hinstance等句柄的存在,其构造函数就执行了对这些成员数据的初始化操作,这里的所谓初始化仅仅是把所有的句柄对象赋值为null。
2)在调用完cwinapp的构造函数以后由连接器向程序内自动链接的afxwinmain函数将被调用,而这个函数可以被看作mfc程序的入口函数。在这个函数当中调用全局afxgetapp()函数获得应用程序对象,这时将调用afxinit全局函数,这个函数的功能是使用操作系统传递给afxwinmain函数的参数初始化应用程序对象当中的相关句柄数据成员。
3)之后afxwinmain函数调用cwinapp::initapplication成员函数,这个成员函数用来初始化应用程序对象当中的关于文档部分的内容。
4)随后调用cwinapp::initinstance成员函数,在这个成员函数当中,使用new操作在堆上声明一个框架窗口对象,由此导致框架窗口对象的构造函数被调用,在框架窗口构造函数当中调用create函数来创建窗口,而调用的create函数一般将wndclass参数设置成null,这样就由mfc内部调用precreatewindow函数,在这个函数当中由mfc注册几个默认的wndclass供框架窗口的create使用。这时程序控制权交还给cwinapp::initinstance成员函数内部,由这个函数调用cwnd::showwindow显示窗口并且调用cwnd::updatewindow向窗口发送wm_paint消息。调用完cwinapp::initinstance成员函数后由afxwinmain函数调用cwinapp::run成员函数,并由这个函数来创建和处理消息循环,并且在没有消息的时候处理onidle空闲处理。至此整个程序的创建过程完成。
5)在程序的运行过程当中,由操作系统源源不断的发送消息给应用程序,并且由cwinapp::run当中的消息循环处理并且分发给相关的窗口对象的defwindowproc成员函数,并由这个成员函数查询窗口对象的消息映射表,如果查到对应项,则由登记在消息映射表当中的类成员函数处理,否则则按照message route当中的顺序象父层类发送。
6)在消息运行结束,用户按下关闭按钮后,操作系统向程序发送wm_close消息,默认状况下程序调用destorywindow并且发送wm_destory消息,应用程序接受到这个消息以后的默认操作是调用postquitmessage函数,由这个函数发送wm_quit消息。当程序对象接受到wm_quit消息后消息循环结束,由afxwinmain函数调用afxterm函数清理程序使用过的资源并且结束整个程序。
三、mfc程序的执行顺序
mfc只是对win32的api进行了封装,所以mfc的本质还是win32程序。有了这层封装,我们看不到win32的winmain函数,也就不清楚mfc程序的启动过程。虽然我们没有看到winmain函数,但不代表没有winmain函数,这个函数位于*\vc\atlmfc\src\mfc目录的appmodul .cpp文件中有一个_twinmain函数, _twinmain函数调用了winmain.cpp文件中的afxwinmain函数。
twinmain函数实现:
extern “c” int winapi
_twinmain(hinstance hinstance, hinstance hprevinstance,
__in lptstr lpcmdline, int ncmdshow)
{
// call shared/exported winmain
return afxwinmain(hinstance, hprevinstance, lpcmdline, ncmdshow);
}1234567
afxwinmain函数实现:
int afxapi afxwinmain(hinstance hinstance, hinstance hprevinstance,
__in lptstr lpcmdline, int ncmdshow)
{
assert(hprevinstance == null);
int nreturncode = -1;
cwinthread* pthread = afxgetthread();
cwinapp* papp = afxgetapp();
// afx internal initialization
if (!afxwininit(hinstance, hprevinstance, lpcmdline, ncmdshow))
goto initfailure;
// app global initializations (rare)
if (papp != null && !papp-》initapplication())
goto initfailure;
// perform specific initializations
if (!pthread-》initinstance())
{
if (pthread-》m_pmainwnd != null)
{
trace(traceappmsg, 0, “warning: destroying non-null m_pmainwnd\n”);
pthread-》m_pmainwnd-》destroywindow();
}
nreturncode = pthread-》exitinstance();
goto initfailure;
}
nreturncode = pthread-》run();
initfailure:
#ifdef _debug
// check for missing afxlocktempmap calls
if (afxgetmodulethreadstate()-》m_ntempmaplock != 0)
{
trace(traceappmsg, 0, “warning: temp map lock count non-zero (%ld)。\n”,
afxgetmodulethreadstate()-》m_ntempmaplock);
}
afxlocktempmaps();
afxunlocktempmaps(-1);
#endif
afxwinterm();
return nreturncode;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445
_twinmain和winmain的声明是一致的,但是_twinmain不是最先执行的,因为整个程序一开始是初始化全局变量,这里的全局变量有winapp类型的theapp,初始化theapp就是执行cwinapp的构造函数。在这个afxwinmain函数里面调用了pthread-》initinstance()函数,initinstance函数是cwinapp类(cwinapp继承于cwinthread)的虚函数,所以这里就调用派生类的initinstance函数,用户一般在这个函数里面创建对话框,单文档,多文档界面。执行完initinstance函数后,就会执行cwinapp类的run函数(*\vc\atlmfc\src\mfc\thrdcore.cpp),这个函数一个死循环,不断地从的消息队列中读取消息。代码如下:
int cwinthread::run()
{
assert_valid(this);
_afx_thread_state* pstate = afxgetthreadstate();
// for tracking the idle time state
bool bidle = true;
long lidlecount = 0;
// acquire and dispatch messages until a wm_quit message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bidle &&
!::peekmessage(&(pstate-》m_msgcur), null, null, null, pm_noremove))
{
// call onidle while in bidle state
if (!onidle(lidlecount++))
bidle = false; // assume “no idle” state
}
// phase2: pump messages while available
do
{
// pump message, but quit on wm_quit
if (!pumpmessage())
return exitinstance();
// reset “no idle” state after pumping “normal” message
//if (isidlemessage(&m_msgcur))
if (isidlemessage(&(pstate-》m_msgcur)))
{
bidle = true;
lidlecount = 0;
}
} while (::peekmessage(&(pstate-》m_msgcur), null, null, null, pm_noremove));
}
}123456789101112131415161718192021222324252627282930313233343536373839
小结
mfc程序的执行顺序和win32的执行顺序完全一样:初始化全局变量(theapp),执行winmain函数(afxwinmain),创建及注册窗口(在initinstance函数中创建对话框,单文档,多文档窗口),消息循环(run函数)
四、mfc程序的执行流程
对于理解mfc程序执行流程,我觉得理解new 、虚拟函数调用、构造和析构函数调用(http://blog.csdn.net/misskissc/article/details/8549254)次序、类指標结论几个地方对此很有帮助。刚好在学习mfc程序的过程中也正好去学习了这几个方面(或者说是先去查询式的学习了这几个方面之后觉得对理解mfc程序执行流程有帮助)。
1.new
new和malloc函数都可以为变量申请一段某种类型的动态存储空间,new在申请动态空间外还会引发申请空间对象构造函数的运行。当然,有new就需要在需要的地方有delete,这是一种必备的习惯。
2.虚拟函数的调用
使用派生类对象访问类的成员函数时,若此函数为非虚拟函数时,访问的函数都是基类成员函数的地址;若访问的成员函数为虚拟函数(前面加了virtual关键字)时,若在派生类中没有申明(修改)则调用的是基类(或者某派生类(当此派生类改变了此虚拟函数时))中的函数地址,若在派生类修改了虚拟函数,则调用的就是派生类的成员函数。
3.构造函数和析构函数的调用次序
构造函数和析构函数的调用次序从一定程度上反应出mfc程序执行流程。建构式(运行应用程序类的对象的构造函数)是早于程序运行入口点的,即应用程序类对象的构造函数的运行要比afxwinmain函数的运行要早。当然,析构函数是在相应情况下(delete发生、局部运行完毕、程序运行结束时)发生。而且构造函数的运行遵循一定的方式(构造函数析构函数调用次序),析构函数的运行与构造函数的运行相反。
4.类指標相关结论
如果以一个基础类别之指标指向衍生类别之物件,那么该指标只能呼叫基础类所定义的函式。如果一个衍生类指标指向一个基础类别物件,必须先做明确的转型动作(对基础类之物件取地址&对派生类指标赋值)。这种做法很危险。如果基础类别和衍生类别都定义了相同名之成员函式,那么透过物件指标呼叫成员函式时,到底呼叫到哪一个函式,必须视该指标原始性别而定,而不是视指标实际所指之物件的性别而定。
五、简析mfc程序执行流程
撇开所有的头文件中对类声明、派生的代码,我们到应用程序类源代码文件(cmy*.cpp)定义应用程序类对象的地方,见图1
图1.《深入浅出 mfc》一书中的mfc程序执行流程分析图
mfc程序执行流程跟标号增大的方向一致:
当定义应用程序类对象时,在程序进入afxwinmain()函数前,应用程序类对象的构造函数先运行,构造应用程序类对象。
进入程序运行的入口点afxwinmain()函数中,afxgetapp()函数返回指向当前应用程序的单一的cwinapp对象的指针。根据类指標相关结论可知,除了被修改的虚拟函式外,用指针papp访问的函数都是访问的cwinapp中的成员函式。所以用papp-》initapplication() [及run] 调用的函式为cwindapp::initapplication() [及cwindapp::run()] ;因为initintance()函数被改变,因此而papp-》initintance()调用的却是派生类中(假设为cmywindapp)的函式(cmywindapp::initintance()),而且initintance()函数内的内容会被自动执行,一般这里的代码为跟生成窗口有关。
这里是关于初始化的函数,作为初学者,这里可以先跳过。容日后究。
这就是在调用派生类cmywindapp的成员函数,虽然这个调用的语句我们看不到。这里涉及虚拟函数妙用的内容哦,派生类中被修改的虚拟函数将会被调用。
(5-8语句)这是在对虚拟函数initintance()进行改写,在改写的过程中很可能依据函数的调用方式(虚拟函数,非虚拟函数的调用方式)再调用其它的函数。其实函数的调用占mfc应用程序执行流程的很大一部分,对于函数的调用,这里添加了一些规则,如此就构成了整个的运行。
(9语句)papp-》run()就是检测是否有消息发生的主消息循环事件函数,当有任何规定的事件发生它都会检测到,并通过一定方式调用到相应的函数,具体内容可以后深究。
这个过程只是在定义应用程序类对象时mfc内部机制的简单剖析,其实在接收到用户信息、调用用响应的事件函数(declare_dynamic/implement_dynamic巨集)等这些过程都是很有研究价值的,对咱初学者来说就先了解一个大概,最开始就要把这些原理弄得很清楚还是很费功夫的!如此,就找到了mfc程序入口及消息循环的基本原理了,至于mfc程序如何响应事件及另外的一些重要性质就让我们日后挖掘吧。
AMD官宣锐龙8040系列处理器 引领AI PC时代
sg3525工频逆变器电路
物联网实验室pk传统实验室
新型超薄电容问世 可制造小型氙气闪光灯
NVIDIA CloudXR 和 Autodesk VRED 已在 AWS 上线
mfc程序执行流程小结,MFC程序的执行顺序
电源测试系统如何测试开关电源反复短路,提高测试效率?
英特尔服务器市场份额高达99% 不惧ARM挑战
车规电容之如何选择正确的电容器(二)
Google允许拥有免费Google帐户的任何人使用Meet
线路板CAD/CAM数控钻孔有什么要求
Waymo发布第五代自动驾驶系统,在物体不动的情况下也能检测到物体
芯片人才培训 4个月冲击年薪45万,有这好事?
共享单车新规正式发布!你是被禁骑的吗?免押金普及到你了吗?骑车保险帮您买了吗?
业界常用的多活手段及各方案优缺点
MacBee技术,你了解多少?
153亿个晶体管–麒麟的巅峰之作
科创板“开闸”运行,知识产权信息披露成关键
土壤养分测定仪是什么,它的作用是什么
如何才能保证电缆使用效果达到更好的标准