mfc程序执行过程剖析

fox38 贡献于2015-03-26

作者 fox  创建于2013-09-02 23:12:00   修改者fox  修改于2013-09-02 23:30:00字数22402

文档摘要:我们知道在WIN32API程序当中,程序的入口为 WinMain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用程序的窗口函数。而在 MFC程序当中我们不在能找到类似WinMain这样的程序入口,取而代之的是一系列派生类的声明和定义以及一个从CWinApp类派生而来的类的全局对 象。CWinApp类被称之为应用程序对象,在一个 MFC程序当中只允许有一个应用程序对象。由于CWinApp的派生对象是全局的,因此这个对象的构造函数会在所有的其他代码运行之前被调用,而由于 CWinApp类当中包含了HWND、HINSTANCE等句柄的存在,其构造函数就执行了对这些成员数据的初始化操作,这里的所谓初始化仅仅是把所有的 句柄对象赋值为NULL。
关键词:

MFC程序执行过程剖析 一 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编程过程中,其实懂得MFC程序中各个函数的执行流程即可。有时候过于追究MFC细节会白白浪费我们的精力,应该将主要精力放在使用MFC解决实际问题上。   二 VC6中SDI程序的执行流程 下面以VC6中的sdi工程为例,通过给每个函数前设置断点后调式执行,可以看出MFC的SDI的执行流程。记录如下,希望对MFC执行有疑惑的人有帮助。 1)CSdiApp theApp;          //sdi.cpp 2)CSdiApp::CSdiApp()             //sdi.cpp 3)BOOL CSdiApp::InitInstance()         //sdi.cpp 4)CSdiDoc::CSdiDoc()                   //sdiDoc.cpp 5)CMainFrame::CMainFrame()          //MainFrm.cpp 6)BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)    //MainFrm.cpp 7)int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)    //MainFrm.cpp 8)CSdiView::CSdiView()                     //sdiView.cpp 9)BOOL CSdiView::PreCreateWindow(CREATESTRUCT& cs)         //sdiView.cpp 10)BOOL CSdiDoc::OnNewDocument()                //sdiDoc.cpp 11) void CSdiView::OnDraw(CDC* pDC)                //sdiView.cpp // ---------------- 关闭窗口后------------------------------------- 12) CSdiView::~CSdiView() 13) CMainFrame::~CMainFrame() 14) CSdiDoc::~CSdiDoc()  http://blog.163.com/hbu_lijian/blog/static/126129153201023152712183/ 1、windows程序内部运行机制 windows程序不同于linux程序,搞了一阵子linux开发,现在又要做windows开发,开始觉得很不适应,不过VC功能之强大不得不令人敬佩。参考书《VC++深入详解》,本文转自网友(感谢这位网友),并自己做了修改。 1、API与SDK Windows操作系统提供了各种各样的函数,以方便我们开发Windows应用程序,这些函数是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数,所有主要的Windows函数都在Window.h头文件中进行了说明 微软提供的API函数大多是有意义的单词组合,每个单词的首字母大写 MSDN是微软为开发人员提供的一套帮助系统,其中包含大量的开发文档、技术文章和示例代码 SDK的全称是Software Development Kit,中文译为软件开发包。SDK实际上就是开发所需资源的一个集合,包括API函数库、帮助文档、使用手册、辅助工具等资源 2、窗口与句柄 窗口是Windows应用程序中一个非常重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接受用户的输入,以及显示输出 一个应用程序窗口通常包括标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条 窗口可以分为客户区和非客户区。客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框统称为窗口的非客户区,它们由Windows系统来管理,而应用程序则主要管理客户区的外观及操作 在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。在Windows程序中,有各种各样的资源(窗口,图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。 3、消息与消息队列 在Windows中,不仅用户程序可以调用系统的API函数,反过来,系统也会调用用户程序,这个调用是通过消息队列来进行的 Windows程序设计是一种基于事件驱动方式的程序设计模式,主要是基于消息的。用户与应用程序交互时,操作系统感知事件,将事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程 1、消息 在Windows程序中,消息是由MSG结构体定义的。MSG结构体的定义如下: typedef struct tagMSG { // msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; 第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口 第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏的形式,WM是Windows Message的缩写,XXX对应某种消息的英文拼写的大写形式。在程序中,我们通常都是以WM_XXX宏的形式来使用消息的 第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。wParam、lParam表示的信息随消息的不同而不同,如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。在VC++开发环境下通过goto definition查看WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long 最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置 2、消息队列 每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应。这种消息机制,就是Windows程序运行的机制 3、进队消息和不进队消息 Windows程序中的消息可以分为“进队消息”和“不进队消息”。进队的消息将由系统放入到应用程序的消息队列中,然后由应用程序取出并发送。不进队的消息在系统调用窗口过程时,直接发送给窗口。不管是进队消息还是不进队消息,最终都由系统调用窗口过程函数对消息进行处理 4、WinMain函数 当Windows操作系统启动一个程序时,调用该程序的WinMain函数(实际是由插入到可执行文件中的启动代码调用的)。WinMain是Windows程序的入口点函数,与DOC程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束 编写一个完整的Win32程序,该程序实现的功能是创建一个窗口,并在窗口中响应键盘及鼠标消息,程序实现的步骤为: WinMain函数的定义 创建一个窗口 进行消息循环 编写窗口过程函数 4.1、WinMain函数的定义 WinMain函数的原形声明如下: int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window ); WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的 第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数 第二个参数hPrevInstance表示当前实例的前一个实例的句柄,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用 第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。要在VC++开发环境中向应用程序传递参数,可以单击菜单Project-Setting,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数 第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值 关于WinMain函数前的修饰符WINAPI,其实就是_stdcall 4.2、窗口的创建 创建一个完整的窗口,需要经过下面几个操作步骤: 设计一个窗口类 注册窗口类 创建窗口 显示及更新窗口 1、设计一个窗口类 一个完整的窗口具有许多特征,包括光标、图标、背景色等。在创建一个窗口前,必须对该类型的窗口进行设计,指定窗口的特征。Windows提供了WNDCLASS结构体来定义窗口特征,该结构体定义好一个窗口所具有的基本属性。WNDCLASS结构体的定义如下: typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; 第一个成员变量style指定了这一类型窗口的样式,常用的样式: CS_HREDRAW 当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口的宽度时,将不会重绘窗口 CS_VREDRAW 当窗口垂直方向上的宽度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口的宽度时,将不会重绘窗口 知识点:在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的类常量,这些常量都只有某一位为1。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式 第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数的实现机制是: 1、定义一个回调函数 2、提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者 3、当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理 针对Windows的消息处理机制,窗口过程函数被调用的过程如下: 1、在设计窗口类的时候,将窗口过程函数的地址付给lpfnWndProc成员变量 2、调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址 3、当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统,系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理 一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一特定的窗口类相关联,基于该窗口类创建的窗口使用同一个窗口过程 lpfnWndProc成员变量的类型是WNDPROC,在VC++开发环境中使用goto definition功能,可以看到WNDPROC的定义: typedef LRESULT (CALLBACK * WNDPROC)(HWND,UINT,WPARAM,LPARAM); LRESULT和CALLBACK实际上是long和_stdcall 从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型 注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同 知识点:在函数调用过程中,会使用栈。_stdcall与_cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。 第三个成员变量cbClsExtra:Windows为系统中的每一窗口类管理一个WNDCLASS结构。在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0 第四个成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0 第五个成员变量hInstance指定包含窗口过程的程序的实例句柄 第六个成员变量hIcon指定窗口类的图标句柄,这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标 在为hIcon变量赋值时,可以调用LoadIcon函数加载一个图标资源,返回系统分配给该图标的句柄。 HICON LoadIcon( HINSTANCE hInstance, // handle to application instance LPCTSTR lpIconName // icon-name string or icon resource // identifier ); LoadIcon的第二个参数是LPCTSTR类型,实际被定义为CONST CHAR *,即指向字符常量的指针,而图标的ID是一个整数。对于这种情况,我们需要MAKEINTERSOURCE宏把资源ID标识符转换为需要的LPCTSTR类型 知识点:在VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑 在VC++中,资源是通过标识符(ID)来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定ID的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX,图标资源为:IDI_XXX。采用这种方式时,我们在程序中使用资源ID时,可以一目了然 WNDCLASS结构体第七个成员变量hCursor指定窗口类的光标句柄,这个成员变量必须是一个光标资源的句柄 第八个成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们可以调用GetStockObject函数来得到系统的标准画刷,GetStockObject函数不仅可以用于获取画刷句柄,还可以用于获取画笔、字体和调色板的句柄 第九个成员变量lpszMenuName是一个以空终止的字符串,制定菜单资源的名字。如果使用菜单资源的ID号,那么需要用MAKEINTERSOURCE宏来进行转换。 要注意,菜单并不是一个窗口 第十个成员变量lpszClassName是一个以空终止的字符串,指定窗口类的名字 2、注册窗口类 设计完窗口类(WNDCLASS)后,需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。注册函数的原型声明如下: ATOM RegisterClass( CONST WNDCLASS *lpWndClass // address of structure with class // data ); 3、创建窗口 设计好窗口类并且将其注册成功之后,就可以用CreateWindow函数产生这种类型的窗口了。CreateWindow函数的原型声明如下: HWND CreateWindow( LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu or child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data ); 参数lpClassName指定窗口类的名称。产生窗口的过程是由操作系统完成的,如果再调用CreateWindow函数之前,没有用RegisterClass函数注册过窗口类,操作系统将无法得知这一类型窗口的相关信息,从而导致创建窗口失败 参数lpWindowName指定窗口的名字。如果窗口样式指定了标题栏,那么这里指定的窗口名字将显示在标题栏上 参数dwStyle指定创建的窗口的样式,要注意区分WNDCLASS中的style成员和CreateWindow函数的dwStyle参数,前者指定窗口类的样式,基于该窗口类创建的窗口都有这些格式,后者是指定某个具体的窗口的样式。下面是几种常用的窗口类型的说明: WS_OVERLAPPED:产生一个层叠的窗口,一个层叠的窗口有一个标题栏和一个边框 WS_CAPTION:创建一个有标题栏的窗口 WS_SYSMENU:创建一个在标题栏上带有系统菜单的窗口 WS_THICKFRAME:创建一个具有可调边框的窗口 CreateWindow函数的参数x,y,nWidthn,Hight分别指定窗口左上角的x,y坐标,窗口的宽度,高度 参数hWndParent指定被创建窗口的父窗口句柄。窗口之间有父子关系,子窗口必须具有WS_CHILD样式 参数hMenu指定窗口菜单的句柄 参数hInstance:指定窗口所属的应用程序实例的句柄 参数lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体 如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接受创建窗口之后返回的句柄值 4、显示及更新窗口 显示窗口 窗口创建之后,调用函数ShowWindow来显示窗口,该函数的原型声明如下: BOOL ShowWindow( HWND hWnd, // handle to window int nCmdShow // show state of window ); 更新窗口 在调用ShowWindow函数之后,紧接着调用UpdateWindow来刷新窗口,该函数的原型声明如下: BOOL UpdateWindow( HWND hWnd // handle of window ); UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到消息队列中,请注意这一点 4.3、消息循环 在创建窗口、显示窗口、更新窗口后,需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,需要调用GetMessage()函数,该函数的原型声明如下: BOOL GetMessage( LPMSG lpMsg, // address of structure with message HWND hWnd, // handle of window UINT wMsgFilterMin, // first message UINT wMsgFilterMax // last message ); 参数lpMsg指向一个消息结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中 参数hWnd指定接收属于哪一个窗口的消息。通常将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息 参数wMsgFilterMin指定要获取的消息的最小值,通常设置为0 参数wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0,则接受所有消息 GetMessage函数接收到除了WM_QIUT外的消息均返回非零值。对于WM_QIUT消息,该函数返回零。如果出现了错误,该函数返回-1 编写的消息循环代码如下: MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } GetMessage函数只有在接收到WM_QIUT消息时,才返回0。此时,while语句判断的条件为假,循环退出,程序才有可能结束运行。在没有接收到WM_QIUT消息时,Windows应用程序就通过这个while循环来保证程序始终处于运行状态 TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数包含的是虚拟键代码和扫描码等信息,而我们在程序中需要得到某个字符的ACSII码,TranslateMessage 函数就可以将WM_KEYDOWN和WM_KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中 DispatchMessage函数分派一个消息到窗口过程,由窗口过程对消息进行处理。DispatchMessage实际上是将消息回传给操作系统,由操作系统调用窗口过程对消息进行处理 Windows应用程序的消息处理机制: 1、操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中 2、应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理 3、应用程序调用DispatchMessage,将消息回传给操作系统。消息由MSG结构体对象来表示,其中包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递 4、系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”) 提示:从消息队列中获取消息还可以调用PeekMessage函数;发送消息可以使用SendMessage和PostMessage函数。SendMessage函数将消息直接发送给窗口,并调用该窗口的窗口过程进行处理。在窗口过程对消息处理完毕后,该函数才返回。PostMessage函数将消息放入到与创建窗口的线程相关的消息队列后立即返回 4.4、编写窗口过程函数 一个Windows应用程序的主要代码部分就集中在窗口过程函数中,该函数的声明形式如下: LRESULT CALLBACK WindowProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); 窗口过程函数的名字可以任意取,但函数定义的形式必须和上述声明的形式一致 WindowProc函数的4个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第1个参数hwnd就标识了接收消息的特定窗口 在窗口过程函数内部使用switch/case语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理 5、 小结 创建一个Win32应用程序的步骤: 编写WinMain函数,可以在MSDN上查找并复制 设计窗口类 注册窗口类 显示并更新窗口 编写消息循环 编写窗口过程函数 6.源代码 //WinMain.cpp #include #include LRESULT CALLBACK WinSunProc(//窗口过程函数声明 HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); int WINAPI WinMain(//win32程序入口函数,查找MSDN HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ) {//主函数体 //设计窗口类,并进行初始化 WNDCLASS wndcls;//定义一个窗口类 wndcls.cbClsExtra=0;//额外类的附加字节数 wndcls.cbWndExtra=0;//窗口的额外附加字节数 wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);//获取背景色,要强制类型转换 wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);//获取光标参数 wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);//获取图标 wndcls.hInstance=hInstance;//应用程序实例号 wndcls.lpfnWndProc=WinSunProc;//窗口处理函数 wndcls.lpszClassName="Weixin2003";//类名 wndcls.lpszMenuName=NULL;//菜单名 wndcls.style=CS_HREDRAW | CS_VREDRAW;//类型,水平重画,垂直重画 //注册窗口类 RegisterClass(&wndcls); //定义一个窗口句柄,并创建窗口 HWND hwnd; hwnd=CreateWindow("Weixin2003","北京维新科学技术培训中心",WS_OVERLAPPEDWINDOW, 0,0,600,400,NULL,NULL,hInstance,NULL); //显示并刷新窗口 ShowWindow(hwnd,SW_SHOWNORMAL); UpdateWindow(hwnd); //定义消息结构体,开始消息循环 MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg);//将虚拟消息转换为字符消息,投入队列 DispatchMessage(&msg);//分派一个消息到窗口,由窗口过程函数对消息进行处理 } return 0; } //编写窗口过程函数 LRESULT CALLBACK WinSunProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { switch(uMsg) { case WM_CHAR://按键消息 char szChar[20]; sprintf(szChar,"char is %d",wParam); MessageBox(hwnd,szChar,"weixin",0); break; case WM_LBUTTONDOWN://鼠标按下消息 MessageBox(hwnd,"mouse clicked","weixin",0); HDC hdc; hdc=GetDC(hwnd); TextOut(hdc,0,50,"计算机编程语言培训",strlen("计算机编程语言培训"));//输出文字 ReleaseDC(hwnd,hdc);//用完之后一定要释放 break; case WM_PAINT://窗口重绘消息 HDC hDC; PAINTSTRUCT ps; hDC=BeginPaint(hwnd,&ps);//为指定窗口进行绘图工作的准备,并用将和绘图有关的信息填充到一个PAINTSTRUCT结构中 /* BeginPaint函数自动设置显示设备内容的剪切区域,而排除任何更新区域外的区域。 该更新区域可以通过InalidateRect或InalidateRgn函数设置,也可以是系统在改变大小、移动、创建、滚动后设置的,   亦是其他的影响客户区的操作来设置的。如果更新区域被标记为可擦除的,BeginPaint发送一个WM_ERASEBKGND消息给窗口。    一个应用程序除了响应WM_PAINT消息外,不应该调用BeginPaint。每次调用BeginPaint都应该有相应的EndPaint函数。   如果被绘画的客户区中有一个^符号,BeginPaint自动隐藏该符号,而保证它不被擦除。    如果窗口类有一个背景刷,BeginPaint使用这个刷子来擦除更新区域的背景。 */ TextOut(hDC,0,0,"维新培训",strlen("维新培训"));//输出文本 EndPaint(hwnd,&ps);//标记指定窗口的绘画过程结束 /* hdc是用于绘制的句柄,fErase如果为非零值则擦除背景,否则不擦除背景,rcPaint 通过制定左上角和右下角的坐标确定 一个要绘制的矩形范围,该矩形单位相对于客户区左上角,后面三个参数都是系统预留的,编程一般用不到。 */ break; case WM_CLOSE://关闭消息 if(IDYES==MessageBox(hwnd,"是否真的结束?","weixin",MB_YESNO)) { DestroyWindow(hwnd);//销毁窗口,当窗口销毁时绘产生WM_DESTROY消息 /* 这个函数通过发送WM_DESTROY 消息和 WM_NCDESTROY 消息使窗口无效并移除其键盘焦点。 这个函数还销毁窗口的菜单,清空线程的消息队列,销毁与窗口过程相关的定时器,解除窗口对剪贴板的拥有权, 打断剪贴板器的查看链。 */ } break; case WM_DESTROY: PostQuitMessage(0); /* 该函数向系统表明有个线程有终止请求。通常用来响应WM_DESTROY消息 PostQuitMessage寄送一个WM_QUIT消息给线程的消息队列并立即返回;此函数向系统表明有个线程请求在随后的某一时间终止。    当线程从消息队列里取得WM_QUIT消息时,应当退出消息循环并将控制返回给系统。 返回给系统的退出值必须是消息WM_QUIT的wParam参数。 */ break; default: return DefWindowProc(hwnd,uMsg,wParam,lParam); /* 该函数调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。 该函数确保每一个消息得到处理。调用DefWindowProc函数时使用窗口过程接收的相同参数。 */ } return 0; } 2、MFC程序框架剖析 2010-03-31 17:34:24| 分类: Windows程序开发 |字号 订阅 1.Cwnd类 Cwnd类是MFC中一个重要的类,它封装了与窗口有关的操作。 2.MFC中的WinMain MFC中的WinMain函数在APPMODUL.cpp中,在编译的时候才会连接进来。 3.theApp全局对象 在程序入口函数加载前,系统已经为全局变量或全局对象分配了内存空间并为它们赋与了初值。对于全局对象,要先调用构造函数对其进行初始化。调用theApp时会选调用设用其父类CwinApp的构造函数。 4.CWinApp 在文件appcore.cpp中 5.AfxWinMain函数 WinMain函数实际上是调用AfxWinMain函数来实现的。AfxWinMain位于WINMAIN.app中。 6.设计和注册窗口 MFC已经为我们预定义了一些默认的标准窗口类,只需要选择所需的窗口类,然后注册就可以了。窗口类的注册是由AfxEndDeferRegisterClass函数完成的。AfxEndDeferRegisterClass函数判断窗口类的类型,然后赋予相应的类名,这些类名都是MFC预定义的。之后计用AfxRegisterClass函数注册窗口类。 AfxRegisterClass函数首先获得窗口柯类信息。如果该窗口类已经注册,则直接返回一个真值;如果尚未注册,就调用RegisterClass函数注册该窗口类。 7.创建窗口 在MFC程序中,窗口的创建功能由Cwnd类的CreateEx函数实现,该函数的声明位于AFXWin.h文件中。实现代码位于WINCORE.CPP文件中。如果在子类的PreCreateWindow函数中修改了CREATESTRUCT结构体的值,那么,接下来调用CreateWindowEx函数时,其参数就会发生相应的改变,从而就会创建一个符合我们要求的窗口。 8.显示及更新窗口 在Test程序的应用程序类(CTestApp)中有一个名为m_pMainWnd的成员变量。该变量是一个Cwnd类型的指针,它保存了应用程序框架窗口对象的指针。也就是说,是指向CmainFrame对象的指针。 在CtestApp类的InitInstance函数内部有如下代码。 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); 这两行代码的功能是显示应用程序框架窗口和更新这个窗口。 9.消息循环 CwinThread类的Run函数就是完成消息循五常这一任务的,该函数是在AfxWinMain函数中调用的。 调用形式如下 pThread->Run(); 10.窗口过程函数 AfxEndDeferRegisterClass函数有这样一句代码。 wndcls.lpfnWndProc=DefWindowProc;这行代码的作用指定一个默认的窗口过程DefWindowProc。但实际上,MFC程序并不是把所有消息都交给DefiWindowProc这一默认的窗口过程来处理的,而是采用一种称之为消息映射的机制来处理各种消息的。 11.MFC程序运行过程 首先利用全局应用程序对象theApp启动应用程序。正是产生了这个全局对象,基类CWinApp中的this指针才能指向这个对象。如果没有这个全局对象,程序在编译时不会出错,但是在运行时就会出错。 调用全局应用程序对象的构造函数,从而就会先调用其基类CWinApp的构造函数后者完成应用程序的一些初始化工作,并将用应程序对象的指针保存起来。 进入WinMain函数。在AfxWinMain函数中可以获取子类(对Test程序来说,就是CtestApp类)的指针,利用此指针调用虚函数:InitInstance,根据多态性原理,实际上调用的是子类(CTestApp)的InitInstance函数。后者完成应用程序的一些初始化工作,包括窗口类的注册、创建、窗口的显示和更新。期间会多次调用CreateEx函数,因为一个单文档MFC应用程序有多个窗口,包括框架窗口、工具条、状态条等。 进入消息循环。虽然也设置了默认的窗口过程函数,但是,MFC应用程序实际上是采用消息映射机制来处理各种消息的。当收到WM_QUIT消息时,退出消息循环,程序结束。 12.文档视类结构 MFC程序除了主框架窗口外,还有一个窗口视类窗口,对应的类是Cview类,Cview类也派生于CWnd类。 CtestDoc类派生于Cdocument类。其基类是CcmdTarget,后者又派生于Cobject类,CtestDoc不是一个窗口类,是一个文档类。 13.窗口类、窗口类对象与窗口 C++窗口类对象与窗口并不是一回事,它们之间惟一的关系就是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄。窗口销毁时,与之对应的C++窗口类对象销毁与否,要看其生命周期是否结束,但C++窗口类对象销毁时,与之相关的窗口也将销毁。 1.CObject类为MFC总类,该类下面有一个重要的类CCmdTarget。 而CCmdTarget类下面又有四个重要的继承类,分别为:CWinThread、CDocument、CDocTemplate、 CWnd类。所以,可以得出一个大概继承图,如图所示: CObject--->CCmdTarget ---->CWinThread ---->CWinApp ----->CDocTemplate ----->CDocument ----->CWnd 其中CWnd类下属又有几个重要的继承类,分别为CFrameWnd、CDialog、CView、Controls、 CWnd---->CFrameWnd ---->CDialog ---->CView ---->Controls 归纳一下,CCmdTarget类为MFC主要类的总钢。应用程序(CWinApp)间接继承于它,文档模板(CDocTemplate)继承于它,文档类(CDocument)继承于它,还有一个非常重要的类窗口类(CWnd)继承于它。 窗口类是一切Windows可见窗口,(包括主窗口,子框窗口,对话框,控件,View窗口)的父类。凡是能可见的,基本上是继承于CWnd,而抽象于其中的(即不可见的)则不继承于它。 2.MFC头文件: ■STDAFX.H:该文件用来作为Precompiled header file,其内只是载入其它的MFC头文件。 ■AFXWIN.H:每个MFC程序都必须载入它,因为它以及它所载入的文件声明了所有的MFC类。此文件内包含AFX.H,后者又包含AFXVER_.H,后者又包含AFXV_W32.H,后者又包含WINDOWS.H ■AFXEXT.H:凡使用工具栏、状态栏的程序必须载入此文件。 ■AFXDLGS.H:凡使用通用型对话框(Common Dialog)的MFC程序要载入此文件。 ■AFXCMN.H:凡使用Windows9x新增的通用型控件(Common Control)之MFC程序需载入此文件。 ■AFXCOLL.H:凡使用Collections Class(处理数据结构如数据,链表类等)之程序需载入此文件。 ■AFXDLLX.H:凡MFC extension DLLs需载入此文件。 ■AFXRES.H:MFC程序的RC文件必须载入此文件。MFC对于标准Windows资源(如File,Edit等)的ID均有默认定义,这此定义在该头文件内。 3.什么是Precompiled Header: 一个应用程序在发展过程中需不断的编译,而Windows程序载入的.H头文件非常巨大且内容不变,编译器如果不Precompiled的话,每次需要编译的时间非常多,所以Precompiled Header就是将.H文件一次编译后的结果存储起来,第二次编译时就可以直接从磁盘中读取。 4.关于应用程序的进入点WinMain和窗口过程WndProc: MFC代码由于类封装了API,所以,根本就看不到程序的进入WinMain和窗口处理函数WndProc,但是,由于这两个函数有相当程序的不变性,所以,MFC就把有着相当固定行为的WinMain内部操作封装在WinApp中,把有着相当固定行为的WndProc内部操作封装在CFrameWnd中。也就是说: ■ CWinApp代表程序本体 ■CFrameWnd代表一个主框窗口(Frame Window) 5.CWinApp类取代了SDK中的WinMain地位: CWinApp本身就代表一个程序本体,程序本体是指与程序本身有关而不与窗口有关的数据或动作。比如,WinMain传递的四个参数,注册窗口类,分派消息等。比如,CWinApp类中就定义了WinMain函数的四个参数(m_hInstance,m_hPrevInstance,m_lpCmdLine,m_nCmdShow)。还定义了一些重要的如 InitApplication(),InitInstance(),Run()等函数。传统的WinMain函数就是由此三个函数完成。 另外,CWinApp中还应该有个主窗口的handle,是的,但它不在CWinApp类中,而是在CWinThread类中实现,在CWinThread类中,有一个指向窗口类的指针的成员变量。如下所示 CWnd* m_pMainWnd 6.CFrameWnd类取代了SDK中的WndProc地位 众所周知,WndProc是用来处理窗口(包括初始化,处理消息,显示等)的函数,那么CFrameWnd也是,首先在头文件中,我们要继承一个CFrameWnd的类,并在此定义要处理的消息(DECLARE_MESSAGE_MAP),然后,在源文件中,定义该类的消息实现(BEGIN_MESSAGE_MAP,END_MESSAGE_MAP) 7.MFC中阴晦的WinMain: 首先,我们必须在源文件中定义了一个CMyWinApp的实体①它是第一步操作。(如: CMyWinApp MyWinApp;注:代码详见:G:\Program\MyMFC\Hello\Hello.dsw), CMyWinApp继承自CWinApp,CMyWinApp无构造函数,但需调用CWinApp构造函数。 配置好MyWinApp实体的一些信息,包括获得该线程的句柄,线程的ID,该实体的句柄等。 配置完后,_tWinMain登场,注意,我们没有撰写WinMain的代码,WinMain是MFC早已准备好并由链接器直接加到应用程序代码中的。_tWinMain是为了支持Unicode而准备的一个宏,实质还是WinMain。好,_tWinMain函数做了什么工作呢?它只是在里面调用AfxWinMain函数。如下:return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); AfxWinMain是一个全局的函数,并不属于任一个类。 下面让我们来看一看AfxWinMain函数做了些什么工作? 第一、它会获得Application Object的指针(亦CWinApp派生对象的指针),在此是获得MyWinApp的指针,以备后用。它是通过下面这句来实现的: CWinApp* pApp = AfxGetApp(); //AfxGetApp为全局函数,不属于任何类,详见第8条... 注:凡是函数前有Afx字样的都是全局函数。 第二、调用AfxWinInit(...)函数,用作MFC GUI程序初始化的一部分,这个函数详见后解... 第三、用上面获得的pApp调用InitApplication()函数,如下:pApp->InitApplication(); InitApplication函数详见后解... 第四、用pApp调用InitInstance()函数,如下:pApp->InitInstance(); InitInstance函数详见后解... 第五、用pApp调用Run()函数,如下: nReturnCode = pApp->Run(); Run函数详见后解... 第六、最后调有AfxWinTerm函数,结束该应用程序。 所以,主要的AfxWinMain做的工作就应该如下代码所示: int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { int nReturnCode = -1; CWinApp* pApp = AfxGetApp(); //详见第8条 AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow); //详见第9条 pApp->InitApplication(); //详见第10条 pApp->InitInstance(); //详见第11条 nReturnCode = pApp->Run(); //详见第12条 AfxWinTerm(); return nReturnCode; } 8.AfxGetApp是如何获得CWinApp继承类(在此即CMyWinApp)的实体的指针的? AfxGetApp是一个全局函数,定义于AFXWIN1.INL中,如下: _AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp() { return afxCurrentWinApp; } 而afxCurrentWinApp则又是一个宏,定于AFXWIN.H中: #define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp 而在CWinApp类的构造函数中,有一段代码是这样定义的: AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); pModuleState->m_pCurrentWinApp = this; 这样,应该看清楚他是如何获得CWinApp继承类(在此是指CMyWinApp类)的实体的指针了吧? 那么,pApp->InitApplication() / pApp->InitInstance() / pApp->Run()就可以理解成这样: CMyWinApp::InitApplication(); / CMyWinApp::InitInstance(); / CMyWinApp::Run() 因为CMyWinApp只继承了InitInstance的实现,所以,就导致调用: CMyApp::InitApplication(); / CMyWinApp::InitInstance(); / CWinApp::Run() 9.AfxWinInit ----AFX内部的初始化操作:② AfxWinInit是CWinApp构造函数后的第一个操作,也就是第二步操作,以下是它的操作摘要: BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow) { ASSERT(hPrevInstance == NULL); AFX_MODULE_STATE* pState = AfxGetModuleState(); pState->m_hCurrentInstanceHandle = hInstance; pState->m_hCurrentResourceHandle = hInstance; // fill in the initial state for the application CWinApp* pApp = AfxGetApp(); //还是获得CMyWinApp实体的指针。 if (pApp != NULL) { // Windows specific initialization (not done if no CWinApp) pApp->m_hInstance = hInstance; //设定WinMain四个参数的初始值。 pApp->m_hPrevInstance = hPrevInstance; pApp->m_lpCmdLine = lpCmdLine; pApp->m_nCmdShow = nCmdShow; pApp->SetCurrentHandles(); } if (!afxContextIsDLL) AfxInitThread(); return TRUE; } 10.CWinApp::InitApplication()函数:③ AfxWinInit函数对内部初始化之后,进入第三步操作:InitApplication,由于CMyWinApp继承自CWinApp,而InitApplication又是CWinApp的一个虚拟函数,我们在CMyWinApp中没有改写它(大部分情况下也不需要改写它),所以我们调用的是CWinApp::InitApplication(),下面我们来看看InitApplication在MFC中做了什么动作? BOOL CWinApp::InitApplication() { if (CDocManager::pStaticDocManager != NULL) { if (m_pDocManager == NULL) CDocManager::pStaticDocManager = NULL; } if (m_pDocManager != NULL) m_pDocManager->AddDocTemplate(NULL); else CDocManager::bStaticInit = FALSE; return TRUE; } 这些操作都是MFC为内部管理而做的。只要记住一点,我们的派生类无需改写它,它是关于CDocManager的类. 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/subleo/archive/2009/08/15/4449823.aspx

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档