MFC首先要知道的--程序执行顺序

2024-09-06 14:18
文章标签 顺序 mfc 知道 程序执行

本文主要是介绍MFC首先要知道的--程序执行顺序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MFC的程序执行顺序

很多刚学MFC的人都会被MFC给弄的晕头转向。以前传统的C语言中的main()不见了,window sdk api 中的WinMain()函数也不见了,到底用MFC编写的程序是如何开始运行的呢?到底MFC有没有遵从最基本的C++的标准呢?到底MFC的代码运行的顺序又是怎么样的呢?那么多个文件,那么多函数,到底哪一个先运行,哪一个后运行,哪一个调用哪一个,哪一个又被哪一个调用(你看晕了吧?那么多“哪一个”^_^)?这里这么复杂,到底最真是的是怎么一回事呢?我开始学习的时候,也是一头雾水,什么都不明白,但是为了能先学习一些其他的,我囫囵吞枣的看了过去,先学习了CDIALOG和CVIEW的一些用法,并能编出了一个很简单的程序。

     前几天,上网的时候,看到好多人都在看《孙鑫vc++讲座》视频教程,好像大家反应还不错,于是,我就去找个地方下载下来看了。刚好今天看到了MFC的运行机制,里面讲到了MFC的运行顺序。孙同学在视频中是利用实例,利用断点,然后不断的进行调试运行,以实事(实事胜于雄辩啊!!!)告诉我们,MFC是如何开始运行的。下面,我就根据我看到的教程,和网上一些前辈整理出来的材料在整理:

  

重点:MFC运行机制   执行顺序 各个函数用途以及调用顺序

孙同学在视频中反复说明的是:MFC的程序和C语言的程序,从执行原理上说,是完全一致的。

抓住这一点,那么对于理解MFC程序的运行机制也就相对于简单了。

C中的main函数就相当于MFC中的WinMain函数。

感兴趣的可以利用VC的断点设置自己跟踪下面讲述的各个函数,就明白它的执行顺序了。

一、C语言程序执行步骤

在C语言中,大约的步骤如下:

1,   全局变量内存分配 例子如下:

#include <iostream.h>

int a=88;

main(){

cout<<a<<endl;

}

如果我们在main前设置断点,我们就会发现,在进入main之前,a就已经存在了。也就是说像a这样的全局变量在进入main函数前已经创建,并初始化。

2,   进入main函数

二、MFC程序的运行步骤(主要是初始化)

打开一个MFC APPWizard(exe)工程,跟踪其执行步骤,可以发现,是以下顺序:

1)CXXApp中的全局变量定义(在WinMain()函数之前定义的全局变量)

CXXApp theApp;

2)调用CXXApp构造函数(当然,创建一个类,它首先会调用自己的构造函数,这时WinMain()还没有运行呢,呵呵奇怪吧?跟上面例子的变量a,其实是差不多的。)

CXXApp ::CXXApp(){}

3)进入Winmain函数(_tWinMain为宏,值为WinMain)(这个函数不是我们自己写的,而且是隐藏在一个比较隐蔽的文件里面,D:/Program Files/Microsoft Visual Studio/VC98/MFC/src/WINMAIN.CPP里面。)

_tWinMain(){} (如果你查看它的定义,#define _tWinMain      WinMain,其实两者是一样的)

4)完成初始化工作:包括窗口类注册、窗口产生、显示和更新

pThread->InitInstance() (由于InitInstance是虚函数,所以这次调用的是派生类的InitInstance()函数,也就是你能在theApp里面看到的那一个函数)

对于MFC程序,MainFrame,View,ToolBar,Controlbar等都是窗口,所以下面的窗口注册与创建、显示等要反复调用多次,一次对应一个窗口

(1) 注册窗口类

AfxEndDeferRegisterClass()(相当于SDK里面的RegisterClass()函数)

(2)创建窗口

CMainFrame::PreCreateWindow()//反复调用一次是给我们修改窗口属性的机会

CFrameWnd::Create()

(3)        消息循环

PumpMessage()

补充1

在MFC中,由于涉及到(窗口)类定义,所以定义全局变量的时候,需要进行更多的步骤。

全局变量涉及到类定义(类似于C中的类型定义)的话,那么需要遵循以下步骤(以MFC的窗口类为例,这是在SDK 里面经常用到的,有用api编写过函数的,应该都知道)

1)   设计一个窗口类

2)   注册窗口类

3)   创建窗口

4)   显示及更新窗口

5)   消息循环  

补充2:本课涉及到MFC函数的源文件位置

根目录

找到您安装VC98下MFC的位置,比如我的机子上为:D:/Program Files/Microsoft Visual Studio/VC98/MFC。下面提供的就是相对路径了。在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码。

1,寻找WinMain人口:

路径:MFC|SRC|APPMODUL.CPP:
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
注意:(#define _tWinMain    WinMain)

2,对于全局对象或全局变量来说,在程序运行即WINMAIN函数加载的时候,已经为全局对象或全局变量分配了内存和赋初值。(理解先运行构造函数CTEAPP:CTEAPP()再运行WinMain的关键)
所以:CTEApp theApp;->CTEApp ::CTEApp(){}->_tWinMain(){}
说明:每一个MFC程序,有且只有一个从WinApp类派生的类(应用程序类),也只有一个从应用程序类所事例化的对象,表示应用程序本身。在WIN32程序当中,表示应用程序是通过WINMAIN入口函数来表示的(通过一个应用程序的一个事例号这一个标识来表示的)。在基于MFC应用程序中,是通过产生一个应用程序对象,用它来唯一的表示了应用程序。

3,通过构造应用程序对象过程中调用基类CWinApp的构造函数,在CWinApp的构造函数中对程序包括运行时一些初始化工作完成了。
CWinApp构造函数:MFC|SRC|APPCORE.CPP
CWinApp::CWinApp(LPCTSTR lpszAppName){...}//带参数,而CTEApp构造函数没有显式向父类传参,难道CWinApp()有默认参数 见下:
(在CWinApp类定义中, CWinApp(LPCTSTR lpszAppName = NULL); )
注意:CWinApp()函数中:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this
(this指向的是派生类CTEApp对象,即theApp)
调试:CTEApp theApp;(  ->    CTEApp ::CTEApp())->    CWinApp::CWinApp() (先调用基类初始化函数)   ->       CTEApp ::CTEApp()     ->      _tWinMain(){}(红色箭头表示依次运行的顺序)

4,_tWinMain函数中通过调用AfxWinMain()函数来完成它要完成的功能。(Afx*前缀代表这是应用程序框架函数,是一些全局函数,应用程序框架是一套辅助生成应用程序的框架模型,把一些类做一些有机的集成,我们可根据这些类函数来设计自己的应用程序)。
AfxWinMain()函数路径:MFC|SRC|WINMAIN.CPP:
在AfxWinMain()函数中:
CWinApp* pApp = AfxGetApp();
说明:pApp存储的是指向WinApp派生类对象(theApp)的指针。
//_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
// { return afxCurrentWinApp; }

调用pThread->InitInstance()
说明:pThread也指向theApp,由于基类中virtual BOOL InitApplication()定义为虚函数,所以调用pThread->InitInstance()时候,调用的是派生类CTEApp的InitInstance()函数。

nReturnCode = pThread->Run();
说明:pThread->Run()完成了消息循环。

5,注册窗口类:AfxEndDeferRegisterClass();
AfxEndDeferRegisterClass()函数所在文件:MFC|SRC|APPCORE.CPP
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister){...}
说明:设计窗口类:在MFC中事先设计好了几种缺省的窗口类,根据不同的应用程序的选择,调用AfxEndDeferRegisterClass()函数注册所选择的窗口类。
调试:CWinApp::CWinApp();->CTEApp theApp;(->  CTEApp ::CTEApp())-> CWinApp::CWinApp()  ->  CTEApp ::CTEApp()  ->   _tWinMain(){}//进入程序  
->   AfxWinMain();  ->   pApp->InitApplication();->    pThread->InitInstance()//父类InitInstance虚函数;->CTEApp::InitInstance()//子类实现函数; ->  AfxEndDeferRegisterClass(LONG fToRegister)//注册所选择的窗口类(出于文档管理,注册提前,正常的应在PreCreateWindow中进行注册)//之后进入创建窗口阶段(以下再不做调试)

6,PreCreateWindow()://主要是注册窗口类,提供这个函数主要是允许程序对窗口的参数进行多次的修改,而且这个函数也是反复调用的。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
   return FALSE;
return TRUE;
}
说明:
CFrameWnd::PreCreateWindow()函数所在文件:MFC|SRC|WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
   VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
    //判断AFX_WNDFRAMEORVIEW_REG型号窗口类是否注册,如果没有注册则注册
   cs.lpszClass = _afxWndFrameOrView;   // COLOR_WINDOW background
    //把注册后的窗口类名赋给cs.lpszClass
}

if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
   cs.style |= FWS_PREFIXTITLE;

if (afxData.bWin4)
   cs.dwExStyle |= WS_EX_CLIENTEDGE;

return TRUE;
}

其中:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);//PreCreateWindow()是个虚函数,如果子类有则调用子类的。这是虚函数的特性。
#define VERIFY(f)           ASSERT(f)
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
define AFX_WNDFRAMEORVIEW_REG           0x00008
const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;//WINCORE.CPP文件中,定义为全局数组。
//#define AFX_WNDFRAMEORVIEW   AFX_WNDCLASS("FrameOrView")

7,创建窗口:
Create()函数路径:MFC|SRC|WINFRM.CPP:
CFrameWnd::Create(...){
...
CreateEx(...);//从父类继承来的,调用CWnd::CreateEx().
...
}

CWnd::CreateEx()函数路径:MFC|SRC|WINCORE.CPP
BOOL CWnd::CreateEx(...){
...
if (!PreCreateWindow(cs))//虚函数,如果子类有调用子类的。
{
   PostNcDestroy();
   return FALSE;
}
...
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

...
}
说明:CreateWindowEx()函数与CREATESTRUCT结构体参数的对应关系,使我们在创建窗口之前通过可PreCreateWindow(cs)修改cs结构体成员来修改所要的窗口外观。PreCreateWindow(cs))//是虚函数,如果子类有调用子类的。
HWND CreateWindowEx(
   DWORD dwExStyle,      
   LPCTSTR lpClassName,  
   LPCTSTR lpWindowName, 
   DWORD dwStyle,        
   int x,                
   int y,                
   int nWidth,           
   int nHeight,          
   HWND hWndParent,      
   HMENU hMenu,          
   HINSTANCE hInstance,  
   LPVOID lpParam        
);
typedef struct tagCREATESTRUCT { // cs 
     LPVOID     lpCreateParams; 
     HINSTANCE hInstance; 
     HMENU      hMenu; 
     HWND       hwndParent; 
     int        cy; 
     int        cx; 
     int        y; 
     int        x; 
     LONG       style; 
     LPCTSTR    lpszName; 
     LPCTSTR    lpszClass; 
     DWORD      dwExStyle; 
} CREATESTRUCT;

8,显示和更新窗口:
CTEApp类,TEApp.cpp中
m_pMainWnd->ShowWindow(SW_SHOW);//显示窗口,m_pMainWnd指向框架窗口
m_pMainWnd->UpdateWindow();//更新窗口
说明:调用的顺序
class CTEApp : public CWinApp{...}
class CWinApp : public CWinThread{...}
class CWinThread : public CCmdTarget
{
...
public:
CWnd* m_pMainWnd;
...
...
}

9,消息循环:
int AFXAPI AfxWinMain()
{ ...
// Perform specific initializations
if (!pThread->InitInstance()){...}
//完成窗口初始化工作,完成窗口的注册,完成窗口的创建,显示和更新。
nReturnCode = pThread->Run();
//继承基类Run()方法,调用CWinThread::Run()来完成消息循环
...
}

CWinThread::Run()方法路径:MFC|SRC|THRDCORE.CPP
int CWinThread::Run()
{ ...
   // phase2: pump messages while available
   do//消息循环
   {
    // pump message, but quit on WM_QUIT
    if (!PumpMessage())//取消息并处理
     return ExitInstance();
    ...
   } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
...
}
说明:
BOOL PeekMessage(,,,,)函数说明
The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
If a message is available, the return value is nonzero.
If no messages are available, the return value is zero.

/
BOOL CWinThread::PumpMessage()
{
...
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))//取消息
{...}
...
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
   ::TranslateMessage(&m_msgCur);//进行消息(如键盘消息)转换
   ::DispatchMessage(&m_msgCur);//分派消息到窗口的回调函数处理(实际上分派的消息经过消息映射,交由消息响应函数进行处理。)
}
return TRUE;
}

9,文档与视结构:
可以认为View类窗口是CMainFram类窗口的子窗口。
DOCument类是文档类。
DOC-VIEW结构将数据本身与它的显示分离开。
文档类:数据的存储,加载
视类:数据的显示,修改

10,文档类,视类,框架类的有机结合:
在CTEApp类CTEApp::InitInstance()函数中通过文档模板将文档类,视类,框架类的有机组织一起。
...
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTEDoc),
RUNTIME_CLASS(CMainFrame),        // main SDI frame window
RUNTIME_CLASS(CTEView));
AddDocTemplate(pDocTemplate);//增加到模板

 

摘自:http://blog.csdn.net/husongchao/article/details/5063786

这篇关于MFC首先要知道的--程序执行顺序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1142240

相关文章

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配(Exact Match)2. 正则表达式匹配(Regex Match)3. 前缀匹配(Prefix Match) 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中,location 指令用于定义如何处理特定的请求 URI。由于网站往往需要不同的处理方式来适应各种请求,NGINX 提供了多种匹

MFC中Spin Control控件使用,同时数据在Edit Control中显示

实现mfc spin control 上下滚动,只需捕捉spin control 的 UDN_DELTAPOD 消息,如下:  OnDeltaposSpin1(NMHDR *pNMHDR, LRESULT *pResult) {  LPNMUPDOWN pNMUpDown = reinterpret_cast(pNMHDR);  // TODO: 在此添加控件通知处理程序代码    if

控制台和MFC中内存泄露工具vld的使用

最近想检测下项目中内存泄露的情况,选中了vld这款。在查找使用方法的时候,大都是控制台下的示例,添加到main函数所在的源文件上。换成MFC就纠结了,不知道添加到哪里去。本文记录控制台和MFC中的使用vld过程。    vld资源:    1)、大家可以移步下边的网址下载:     http://vld.codeplex.com/releases/view/82311    2

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

PNG透明背景按钮的实现(MFC)

问题描述: 当前要在对话框上添加一个以两个PNG图片作为背景的按钮,PNG图的背景是透明的,按钮也要做出相同的透明效果。并且鼠标不在按钮上时,按钮显示"bg1.png";鼠标移动到按钮上时,按钮显示"bg2.png" 开发环境为VS2010。 解决办法: 使用GDI+库装载PNG图片,并使用MFC Button Control和CMFCButton类结合,调用CMFCButton

MFC 控件重绘(2) NM_CUSTOMDRAW, WM_DRAWITEM, 虚函数DrawItem

控件重绘有三种方法: 1 设定界面属性 2 利用Windows的消息机制,通过Windows消息映射(Message Mapping)和反映射(Message Reflecting),在合适的时机修改控件的状态和行为。此方式涉及NM_CUSTOMDRAW和WM_DRAWITEM 3 利用虚函数机制,重载虚函数。即DrawItem虚函数。 对于NM_CUSTOMDRAW,某些支持此消息的控件

[数据结构]队列之顺序队列的类模板实现

队列是一种限定存取位置的线性表,允许插入的一端叫做队尾(rear),允许删除的一端叫做队首(front)。 队列具有FIFO的性质 队列的存储表示也有两种方式:基于数组的,基于列表的。基于数组的叫做顺序队列,基于列表的叫做链式队列。 一下是基于动态数组的顺序队列的模板类的实现。 顺序队列的抽象基类如下所示:只提供了接口和显式的默认构造函数和析构函数,在派生类中调用。 #i

[数据结构]栈之顺序栈的类模板实现

栈的数组实现形式,采用动态分配数组,不够时可以调整栈的大小。 Stack.h文件:主要定义栈的抽象基类,提供公共的接口函数。 #ifndef STACK#define STACK//栈的抽象基类template<class T>class Stack{public:Stack(){}~Stack(){}virtual void Push(const T& x)=0;virt

C++中类的构造函数调用顺序

当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的 构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止。 简而言之,对象是由“底层向上”开始构造的。因为,构造函数一开始构造时,总是 要调用它的基类的构造函数,然后才开始执行其构造函数体,调用直接基类构造函数时, 如果无专门说明,就调用直接基类的默认构造函数。在对象析构时,其顺序正好相反。