mfc程序执行流程小结,MFC程序的执行顺序

电子常识

2585人已加入

描述

  摘要:本文章主要以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

  mfc

  图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程序如何响应事件及另外的一些重要性质就让我们日后挖掘吧。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分