资源描述
Windows应用程序框架结构,哈尔滨工程大学,概述,理解Window编程所使用的事件驱动模型 Window编程的基本框架,Windows平台下可视化开发工具,可视化开发系统集成了一系列系统可用资源和开发工具 1、程序调试工具包括源程序语法检查、可执行程序修改和运行监视等 2、源程序编辑器和编译器 3、资源管理器,包括图形化窗口及组成元素的多种对象的编辑器 4、系统函数库和系统函数开发工具 5、可选择并构成具体语句或源程序结构的例程库及Help,Windows程序的特点,大致说来windows编程有两种方法: a.windows c方式(SDK),SDK编程就是直接调用windows的API进行编程; b.c+方式:即对SDK函数进行包装,如VC的MFC,BCB的OWL等。MFC把这些API封闭起来,共有一百多个类组成. API,全称application program interface,意思是应用程序编程接口(说起API并不仅仅指windows而言, windows支持的API叫winapi)。winapi就是应用程序和windows之间通讯的一个编程界面。windows提供了上千个API函数,以方便程序员来编写应用程序。,Windows程序的特点,WinSDK程序设计就是API方式的windows程序设计。SDK,全称Software Developers Kit,意思是软件开发工具箱。 MFC,全称Microsoft Foundation Classes,伪软把WinAPI进行封装的类库。它是一个类的集合,通过覆盖WinAPI,为编程提供了一个面向对象的界面。它使windows程序员能够利用C+面象对象的特性进行编程,类似BCB的OWL,Delphi的VCL组件。它把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现。你不妨把它想象成类似TC提供的函数库吧。,SDK编程,利用Windows API函数编写Windows应用程序必须首先了解以下内容: (1)窗口的概念 (2)事件驱动的概念 (3)句柄 (4)消息,Windows的事件驱动机制,Dos的过程驱动与Windows的事件驱动 在讲Window消息循环之前,我想先谈一下Dos与Windows驱动机制的区别: DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。 而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,作为一个windows程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。,过程驱动方法和事件驱动方法,Dos编程和Windows编程不同,dos下的C编程的main()一样,windows下的入口是WinMain()函数。 WinMain()所起的作用:初始化,展示窗口,销毁应用程序等。 第一个参数:应用程序的当前实例句柄。 第二个参数:应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL. 第三个参数:指向任何传给程序的命令行参数。PSTR代表“指向字符串的指针“。 第四个参数:它告诉应用程序如何初始化窗口,如最大化,最小化等状态。,WinMain函数的功能,三个基本的组成部分:函数说明、初始化和消息循环,WinMain函数,注意!Win是多任务管理的,同一应用程序的多个窗口可能会同时存,Win系统对每个窗口的执行称为一个实例,并用一个实例句柄来唯一标识,Windows常见的数据类型,在Windows.h中定义了Windows 应用程序中包含种类繁多的数据类型,重要的数据结构,两者句柄定义的不同,句柄(handle): 在标准C库中句柄用来对文件输入输出。 在Windows环境中,句柄是用来标识项目的,这些项目包括: *.模块(module) *.任务(task) *.实例(instance) *.文件(file) *.内存块(block of memory) *.菜单(menu) *.控制(control) *.字体(font) *.资源(resource),包括图标(icon),光标(cursor),字符串(string) *.GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域(region),以及设备描述表(device context)。 WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。,句柄是什么?,在Win32里,句柄是指向一个无值型对象(void *)的指针,是一个4字节长的数据”。句柄并不是一个真正意义上的指针。从结构上看,句柄的确是一个指针,尽管它没有指向用于存储某个对象的内存位置,而实际上句柄指向的是一个包含了对该对象进行的引用的位置。我们天气热摇扇子的时候只要抓住扇柄便可以控制整个扇子的运动了,在程序中也差不多是这个意思。通常一个句柄就可以传递我们所要做的事情。有经验的开发者肯定清楚,编写程序总是要和各种句柄打交道的,句柄是系统用来标识不同对象类型的工具,如窗口、菜单等,这些东西在系统中被视为不同类型的对象,用不同的句柄将他们区分开来。,句柄,常用句柄类型及其说明,应用程序通过 句柄访问相应 的对象信息,HWND 窗口句柄 HDC 设备环境句柄 HBITMAP 位图句柄 HCURSOR 光标句柄 HICON 图标句柄 HFONT 字体句柄 HMENU 菜单句柄 HPEN 画笔句柄 HFILE 文件句柄 HBRUSH 画刷句柄 HINSTANCE 当前实例句柄,Windows程序的特点,窗口句柄: 系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。 所有的命名采用了匈牙利表示法。如消息的前缀使用msg.句柄使用h.函数使用fn等。 Windows程序则至少两个主程序, 一个是WinMain(), int WINAPI WinMain( HINSTANCE hInstance, / handle to current instance HINSTANCE hPrevInstance, / handle to previous instance LPSTR lpCmdLine, / command line int nCmdShow / show state );,Windows程序的特点,另一个是窗口过程函数WndProc,它的函数原型为: long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam); 窗口函数与回调函数: 在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WndProc。应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。WndProc是一个主回调函数,Windows至少有一个回调函数。典型的回调函数有窗口过程、对话框过程和钩子函数。实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都有一个与之相对应的窗口过程。 实例:在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。,Windows程序的特点,WinMain()函数的调用约定是PASCAL。 在这里PASCAL是一个调用约定,由于这种方式最早由PASCAL采用,所以这么叫。 在MSDN中的C+ Language Reference中,Calling Conventions这一章都是讲调用约定的。 约定:微软重定义了许多约定类型,为的是可以让代码更容易跨平台或者跨编译器。 其实,调用约定要解决两个问题,都是针对堆栈操作: 1。参数传递的顺序(本质是压栈的顺序) 2。谁负责平栈(调用者还是调用对象) 一个函数的声明、定义和实现中的调用方式一般都一致。 WINAPI标识符的定义是:#define WINAPI _stdcall, _stdcall指Window调用函数的一种方式,也就是如何在堆中存取函数参数的方式。许多Windows Api函数调用声明为_stdcall方式。,调用方式,1、_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上“”和参数的字节数(堆栈要求分配的字节数 )。 如:_TestMethod4 2、C调用约定(即用_cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。如 :_TestMethod,调用方式,CALLBACK和WINAPI函数是由你设计的函数,但你不能“显式”的调用它,而是Windows 系统自己调用,例如窗口过程函数,枚举函数等,这是由Windows系统的工作机制决定的。 至于这些函数为什么要是CALLBACK,且看下面详解: CALLBACK,WINAPI等在windef.h中被如下定义: #define CALLBACK _stdcall #define WINAPI _stdcall #define WINAPIV _cdecl #define APIENTRY WINAPI #define APIPRIVATE _stdcall #define PASCAL _stdcall,调用方式举例,CALLBACK就是要VC编译器在编译时采取非默认(_cdecl)的方式(_stdcall),_stdcall和_cdecl最大的不同就是我前面说的“堆栈指针恢复的责任归属”-C编译器默认(_cdecl)是主函数来恢复stack指针SP,若指定关键字_stdcall,则编译器生成的码是由被调函数自己恢复SP,看看下面的例子:假如有一个函数声明如 int _stdcall getMax(int a ,int b); 返回两者中较大值。在主函数中被调用,调用时VC造出的码如下:(其它编译器可能不同) 0040102B push 38h ;参数b 0040102D push 60h ;a 0040102F call ILT+15(_getMax)(0x0040100f) ; call getMax() . . 为 getMax()造出的汇编语句是 00401020 push ebp 00401021 mov ebp,esp . . . .;其它指令 00401026 pop ebp 00401027 ret 8 ;在返回时修改堆栈指针,调用方式举例,若声明及定义时无 _stdcal,VC造出的码如下: 0040102B push 38h 0040102D push 60h 0040102F call ILT+15(_getMax)(0x0040100f) 00401034 add esp,8 ;esp由主函数恢复 . . 为 getMax()造出的汇编语句是 00401020 push ebp 00401021 mov ebp,esp . . . .;其它指令 00401026 pop ebp 00401027 ret ;返回 现在你该知CALLBACK 与不加CALLBACK 的函数在调用以及返回上有什么区别了吧。,Windows程序的特点,用位的“或”操作(操作符“|”)把若干个常数组合起来控制消息窗口显示的按钮和图标等。在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1位为1。在VC+开发环境中,可以看到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200,将这些16进制数转换为2进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或()操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW | CS_VREDRAW。假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反()操作,然后再和这个变量进行与(&)操作即可实现。例如,要去掉先前的style变量所具有的CS_VREDRAW样式,可以编写代码:style=style & CS_VREDRAW。 在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。,Windows程序的特点,在Windows应用程序中,每一个窗口都必须从属于一个窗口类,窗口类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名称及窗口过程名等。 窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在建立窗口类后,必须向Windows登记.建立窗口类就是用WNDCLASS结构定义一个结构变量。,窗口的创建过程,窗口的创建过程需要四个步骤,下面列出了创建步骤和这个过程中涉及的类和函数: 1.设计一个窗口类/很多特征(光标,图标,背景)WNDCLASS 2.注册窗口类/RegisterClass 3.创建窗口/首先定义句柄 如:HWND hwnd; CreateWindow 4.显示及更新窗口/显示窗口:ShowWindow,更新窗口:UpdateWindow,理解Window编程所使用的事件驱动模型,事件,消息,消息队列和消息循环 Windows应用程序是遵循事件驱动模型,通常Windows程序启动后只是等待某些事件的发生(当然应用程序也可以进行空闲处理-当没有事件发生时(WM_IDLE)执行某些特定的任务)。事件有多种产生途径,包括: 键盘中某些键按下,鼠标单击,当窗口创建时,尺度发生变化时(Windows系统发给你的),发生移动,被关闭,最小化,最大化或变为可见状态时等。 当某一个事件发生时,Windows会为该事件所针对的应用程序发送一条消息,表明该事件发生了,并在该应用程序的消息队列中增加一条消息,该消息队列只是保存了应用程序所接受的消息的一个优先队列(会根据消息的优先级排列)。应用程序要做的事,就是在一个消息循环中不断地检查消息队列,当接收到一条消息时,便将其分派给接收该消息的特定窗口的窗口过程。一个应用程序可能含有很多的窗口,窗口过程是一个与应用程序各窗口相关的特殊函数。(每一个窗口都有一个窗口过程,但多个窗口可以共用一个窗口过程),他就是我们实现的用于处理消息的函数。,事件驱动编程模型,事件,Window操作系统检测到 某一个事件的发生。作为 响应,Windows将给特定程 序的消息队列发送一条消息,应用程 序A消 息队列,应用程 序B消 息队列,应用程 序C消 息队列,当一条消息到来时,消息队 列会一直将该消息保存到应用程序准备 对其进行处理为止。所以,如果应用层 序正处在忙碌状态,该消息便不会立即被 处理。此外,优先级较高的消息会在优先级 较低的消息之前优先得到处理。,应用程序A的 消息循环,应用程序B的 消息循环,应用程序C的 消息循环,窗口过程C,窗口过程C,窗口过程C,应用程序的消息循环不断地检查消 息是否到来,当“获取”一条 消息时,该消息便被发送给窗口过 程WndProc,该函数为消息的传递的目 的地。,窗口过程是一个由应用程序开 发人员定义的函数。该函数由windows 系统自动调用。注意,一个单个应 用程序允许有多窗口函数,Case WM_CREATE: Case WM_KEYDOWN: Case WM_DESTROY: 在窗口处理函数内部,编 写消息处理操作,消息?,消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。 消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息。 消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。,消息中有什么?,它在Windows中声明如下: typedef struct tagMsg HWND hwnd; 接受该消息的窗口句柄 UINT message; 消息常量标识符,也就是我们通常所说的消息号 WPARAM wParam; 32位消息的特定附加信息,确切含义依赖于消息值 LPARAM lParam; 32位消息的特定附加信息,确切含义依赖于消息值 DWORD time; 消息创建时的时间 POINT pt; 消息创建时的鼠标/光标在屏幕坐标系中的位置 MSG;,Msg消息结构内容,Windows系统的消息机制都包含2个长整型的参数:WPARAM, LPARAM,可以存放指针,也就是说可以指向任何内容了。 传递的内容因消息各异,消息处理函数会根据消息的类型进行特别的处理,它知道传递的参数是什么含义。 消息在线程内传递时,由于在同一个地址空间中,指针的值是有效的。但是夸线程的情况就不能直接使用指针了,所以Windows系统提供了 WM_SETTEXT, WM_GETTEXT, WM_COPYDATA等消息,用来特殊处理,指针的内容会被放到一个临时的内存映射文件(Memory-mapped File)里面,通过它实现线程间的共享数据。,消息队列和线程的关系是什么?,消息队列的创建:当一个线程第一次被创建时,系统假定线程不会用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦该线程调用一个与图形用户界面有关的函数 ( 如检查它的消息队列或建立一个窗口 ),系统就会为该线程分配一个消息队列 ,以便它能够执行与用户界面有关的任务。 操作系统可能为任何线程创建消息队列,只要该线程调用了消息获取函数,甚至都不需要该线程创建任何窗口。 如果句柄是NULL,DispatchMessage不做任何事。因此在处理线程消息的时候不能用DispatchMessage处理,而是直接处理,线程一般没有窗口。如下: DispatchMessage函数需要输入一个指向MSG结构的指针(当然这个MSG结构可以是以前GetMessage或者PeekMessage获得的)。DispatchMessage会传送窗口句柄,消息标识符,两个消息参数给窗口过程函数,但是他不会传送时间和鼠标位置信息。应用程序在处理消息的时候可以通过调用GetMessageTime和GetMessagePos函数获得这两个信息。,消息队列的资源体,线程处理消息的例程,如果参数lpmsg指向一个WM_TIMER消息,并且WM_TIMER消息的参数IParam不为NULL,则调用IParam指向的函数,而不是调用窗口程序。 见线程multi1thread1中的 CreateMsgThread和GetMesgThread 函数中调用PeekMessage的用意。,没有窗口如何发消息?,向一个线程传递消息可以使用PostThreadMessage,这个函数的接口很明确,要求传递一个线程Id,但是还有PostMessage和SendMessage这样的API,要求传递的是窗口句柄,这可能就是我们此前会迷惑于消息队列是跟窗口相关还是跟线程相关的原因。事实上,在调用PostMessage或SendMessage的时候,系统要根据传入的窗口句柄,确认是哪一个线程创建了该窗口(我们也可以通过GetWindowsThreadProcessId自行获取是哪个线程创建了一个窗口),然后系统分配一块内存,将消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。 线程中可以有消息循环,消息循环将线程中的消息取出来并且进行处理可以自行根据消息的类型进行处理,也可以交给DispatchMessage处理,该API会回调窗口类中的窗口处理函数(依据该窗口所属窗口类别WNDCLASS的不同分别回调不同的消息处理函数)。如果线程创建了窗口,那么窗口的各种响应事件全部是由消息循环以及相关处理完成的,一个消息循环可以处理很多个窗口的消息。,消息分类,队列消息和非队列消息:从消息的发送途径上看,消息分两种:队列消息和非队列消息。 队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。这里,对消息队列阐述如下: Windows维护一个系统消息队列,每个GUI线程有一个线程消息队列(Thread message queue)。鼠标、键盘事件由鼠标或键盘驱动程序转换成输入消息并把消息放进系统消息队列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次从系统消息队列移走一个消息,确定它是送给哪个窗口的和这个窗口是由哪个线程创建的,然后,把它放进窗口创建线程的线程消息队列。线程消息队列接收送给该线程所创建窗口的消息。线程从消息队列取出消息,通过Windows把它送给适当的窗口过程来处理。除了键盘、鼠标消息以外,队列消息还有WM_PAINT、WM_TIMER和WM_QUIT。这些队列消息以外的绝大多数消息是非队列消息。,消息分类?,windows中的消息虽然很多,但是种类并不繁杂,大体上有3种:窗口消息、命令消息和控件通知消息。 窗口消息大概是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。 命令消息,这是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。 控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。 她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。,常见的window消息,VC中存在几种系统定义的消息分类,不同的前缀符号经常用于消息宏识别消息附属的分类,系统定义的消息宏前缀如下:,BM 表示按钮控制消息 CB 表示组合框控制消息 DM 表示默认下压式按钮控制消息 EM 表示编辑控制消息 LB 表示列表框控制消息 SBM 表示滚动条控制消息 WM 表示窗口消息,Windows应用程序常用消息,WM_LBUTTONDOWN:产生单击鼠标左键的消息,Windows应用程序常用消息,此外,相似的消息还有: WM_LBUTTONUP:放开鼠标左键时产生; WM_RBUTTONDOWN:单击鼠标右键时产生; WM_RBUTTONUP:放开鼠标右键时产生; WM_LBUTTONDBLCLK:双击鼠标左键时产生; WM_RBUTTONDBLCLK:双击鼠标右键时产生。,2.WM_KEYDOWN:按下一个非系统键时产生的消息,系统键是指实现系统操作的组合键,例如Alt与某个功能键的组合以实现系统菜单操作等,wParam:按下键的虚拟键码,用以标识按下或释放的键 1Param:记录了按键的重复次数、扫描码、转移代码、先前键的状态等信息。,如F1的虚拟键码在Windows.h文 件中定义为VK_F1,相似的消息还有WM_KEYUP, 在放开非系统键时产生,Windows应用程序常用消息,3.WM_ CHAR:按下一个非系统键时产生的消息 wParam 为按键的ASCII码 1Param 与WM_KEYDOWN的相同 4. WM_CREATE:由CreateWindow函数发出的消息 wParam:未用 1Param:包含一个指向CREATESTRUCT数据结构的指针 5.WM_CLOSE:关闭窗口时产生的消息 wParam和1Param均未用。 6.WM_DESTROY:由DestroyWiodow函数发出的消息,Windows应用程序常用消息,7. WM_QUIT:由PostQuitMessage函数发出的消息 退出应用程序时发出的消息 wParam:含退出代码,标识程序退出运行时的有关信息 1Param:未用 8. WM_PAINT,消息的接收,消息的接收主要有2个函数:GetMessage、PeekMessage。 GetMessage原型如下:BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax); GetMessage该函数用来获取与hWnd参数所指定的窗口相关的且wMsgFilterMin和wMsgFilterMax参数所给出的消息值范围内的消息。需要注意的是,如果hWnd为NULL,则GetMessage获取属于调用该函数应用程序的任一窗口的消息,如果wMsgFilterMin和wMsgFilterMax都是,则GetMessage就返回所有可得到的消息。函数获取之后将删除消息队列中的除WM_PAINT消息之外的其他消息,至于WM_PAINT则只有在其处理之后才被删除。,消息的处理,接下来我们谈一下消息的处理,首先我们来看一下VC中的消息泵: while(GetMessage( 首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。,消息的处理,处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则GetMessage返回,从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的WM_DESTROY消息中调用。,窗口过程,窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。 系统发送消息给窗口过程将消息数据作为参数传递给他,消息到来之后,按照消息类型排序进行处理,其中的参数则用来区分不同的消息,窗口过程使用参数产生合适行为。 一个窗口过程不经常忽略消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须return一个值作为它的消息处理结果。大多数窗口只处理小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。窗口过程被所有属于同一个类的窗口共享,能为不同的窗口处理消息。,小结,如果想在按下ESC键后,销毁某一个窗口,则在窗口处理函数中,可以这样写: switch(uMsgId) case WM_KEYDOWN: if (wParam=VK_ESCAPE) :DestroyWindow(MainWndhandle); Return 0; 该窗口没有处理的消息交给默认的窗口过程DefWindowProc处理。 用户或者应用程序的某些行为(调用一个API)产生一些事件,操作系统找到事件所属的应用程序,然后向该应用程序发送一条相应的消息。然后,该消息就被加入到该应用程序的消息队列中。之后,应用程序不断地检查消息队列。每当接受到一条消息时,应用程序就会将该消息发给与该消息所属窗口相关的窗口过程。最后,窗口过程执行与当前消息对应的指令。,Window程序的运行机制,基本上是这样运行的,程序从WinMain()开始,然后进入一个message loop,程序在这里等待发给它的所有消息然后一一处理,直到接收到WM_QUIT的消息的时候,message loop终止,程序结束。所以整个主程序运行的过程就是等待消息,接收消息,然后处理消息的过程。,理解Window编程所使用的事件驱动模型,Windows程序基本框架 Windows程序具有相对固定的结构。与以前的DOS程序不同。Windows程序和操作系统结合更紧密。可以说应用程序的在运行时绝大多数时间都在等待操作系统的消息。我们编写一个Windows程序其实很简单。需要做的工作是:注册窗口类,创建窗口对象,编写窗口过程,消息循环。,1:注册窗口类,Windows的窗口类定义了一个窗口的风格,而类中定义的窗口过程决定了窗口的行为。每个类都有一个自己的名字,在Windows中所有的窗口都属于一个窗口类。如果我们需要创建一个自己的窗口,则必须注册一个窗口类。Win32系统提供了一些全局类。这些类无需注册就可以使用。比如:Windows的一些通用控件。对话框类。等等。下面是一个注册窗口类的代码: WNDCLASS wc; memset( 上面的代码 WNDCLASS 是一个结构体,我们通过设置它的成员的值来设定窗口类的风格和属性。然后将这个结构体传递给系统,申请注册窗口类。成功返回非0值,失败返回0.如果注册成功。我们就可以利用该类名来创建窗口了。,2:注册窗口类,创建窗口就比较简单了。我们只需要调用CreateWindow函数,就可以创建它。用户要注意的是,在这里要了解每个参数的作用。 HWND CreatemdiWndClassWnd(void) HWND hwnd=CreateWindow(“MyWndClass”,“MDI 多文档”,/这两个参数,第一个为类名,就是我们前面注册的窗口类名,第二个为窗口标题 。 WS_MINIMIZEBOX|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_MAXIMIZEBOX|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_THICKFRAME,/这里是窗口风格,我们可以在这里指定窗口是否有最大化按钮,是否有标题栏,是否可调整边框等等一些风格 CW_USEDEFAULT,0,CW_USEDEFAULT,0,/这里是指定窗口的坐标和尺寸 NULL, NULL, hInst,/实例句柄 NULL); ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; return hwnd; 通过上面这个函数,我们已经创建了一个窗口,创建完毕后还不能显示,需要调用ShowWindow()函数窗口就会正常显示出来。 在返回前,CreateWindow给窗口过程发送一个WM_CREATE消息。对于层叠,弹出式和子窗口,CreateWindow给窗口发送WM_CREATE,WM_GETMINMAXINFO和WM_NCCREATE消息。消息WM_CREATE的IParam参数包含一个指向CREATESTRUCT结构的指针。如果指定了WS_VISIBLE风格,CreateWindow向窗口发送所有需要激活和显示窗口的消息。,3:窗口过程,窗口过程是我们处理消息的关键。前面我们说过,我们的应用程序的大部分工作都由操作系统完成,我们只要对自己感兴趣的事件处理即可。在注册窗口类的一节我们看到过了。窗口过程是一个函数,它在注册窗口类的时候被传递。 LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) switch (msg) case WM_CREATE: /这里是窗口创建完毕的消息,每一个窗口创建完毕都会产生次事件 /do something 做我们需要做的工作。完了要break; break; case WM_CLOSE:/当用户点击关闭按钮的时候会触发这个事件。 DestroyWindow(hWin); return TRUE; case WM_DESTROY: PostQuitMessage(NULL); break; default: return DefWindowProc(hWin,uMsg,wParam,lParam);/我说了。大部分工作由系统处理吧,不感兴趣的消息。统统交给默认窗口过程。 return 0; ,窗口过程,上面的窗口过程很简单吧。窗口过程是一个回调函数,也就是说。它是由操作系统调用的。当我们点击一个鼠标或者按下一个键后,操作系统会将消息发送给前台窗口,应用程序接收到此消息后,会调用对应的窗口过程。这个时候窗口过程被执行。 响应WM_DESTROY,调用PostQuitMessage(int)结束进程。它会投递一个WM_QUIT消息对消息队列中。当消息循环的GetMessage取到WM_QUIT消息,则返回0,程序结束。 所以说应用程序会自己产生消息。,4:消息循环,消息循环顾名思义,就是循环等待消息的到来。 while (GetMessage ( 这里是一个最简单的消息循环了。GetMessage 是从消息队列种取得一条消息,如果消息队列中没有消息,这个函数就等待,直到有消息到达才会返回。当然如果收到WM_QUIT 消息,这个函数会返回假,循环跳出,这个时候就宣告应用程序关闭了。窗口过程中的 PostQuitMessage() 就是发送这个消息。通知退出消息循环。TranslateMessage () 函数的功能是对一些原始消息进行预处理,比如按键消息DispatchMessage ()函数则是负责分派消息,它会将消息的参数做为调用参数,调用该窗口的窗口过程。,WinMain函数,WinMain函数是所有windows应用程序的入口,类似于C语言中的Main函数,其功能是完成一系列的定义和初始化工作,并产生消息循环。消息循环是整个程序运行的核心。WinMain函数实现以下功能。 1. 注册窗口类,建立窗口及执行其它必要的初始化工作; 2. 进入消息循环,根据从应用程序消息队列接受的消息,调用相应的处理过程 3. 当消息循环检索到WM_QUIT消息时终止程序运行。 WinMain函数有三个基本的组成部份:函数说明、初始化和消息循环。 WinMain函数的说明如下: int WINAPI WinMain( /WinMain函数说明 HINSTANCE hInstance, /程序当前实例句柄 HINSTANCE hPrevInstance, /应用程序其它实例句柄 LPSTR lpCmdLine, /指向程序命令行参数的指针 int nCmdShow /应用程序开始执行时窗口显示方式的整数值标识 ) 由于Window操作系统是多任务的操作系统,能进行多任务的管理,因此,windows应用程序可能被并行的多次执行,因而可能出现同一个程序的多个窗口同时存在的情况,Windows系统将应用程序每一次执行称为该应用程序的一个实例(Instance),并用一个实例句柄唯一的标识它。 #define WINAPI _stdcall,指定函数的调用约定,即函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。,HelloWORLD示例程序的解释,int APIENTRY WinMain(HINSTANCE hInstance, /WinMain函数说明 HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) / TODO: Place code here. /* 建议采用Pascal的变量定义风格,即在程序(函数)开始处定义所有变量 虽然C的变量定义比较灵活,本程序为了使程序易于理解,未采用这种方法,便于移植。 */ char lpszClassName=“窗口“; /窗口类名 char lpszTitle=“Windows SDK编程之一 窗口示例程序“; /窗口标题名 /-窗口类定义- /* 窗口类的定义 在Windows应用程序中,窗口害定义了窗口的形式与功能。窗口类定义通过给窗口类数据结构WNDCLASS赋值完成,该数据结构中包括窗口类的各种属性,在窗口类定义过程中常用到以下函数: */,WNDCLASS wndclass; wndclass.style=0; /窗口类型为缺省类型 wndclass.lpfnWndProc=WndProc; /窗口处理函数为WndProc wndclass.cbClsExtra=0; /窗口类无扩展 wndclass.cbWndExtra=0; /窗口实例无扩展 wndclass.hInstance=hInstance; /当前实例句柄 wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); /使用缺省图标 /* LoadIcon():在应用程序中加载一个窗口图标 LoadIcon()函数原型为: HICON LoadIcon( HINSTANCE hInstance,/图标资源所在的模块句柄,为NULL则使用系统预定义图标 LPCTSTR lpIconName /图标资源名或系统预定义图标标识名 )*/,实例,实例,wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); /窗口采用箭头光标 /* LoadCursor():在应用程序中加载一个窗口光标 LoadCursor()函数原型为: HCURSOR LoadCursor( HINSTANCE hInstance,/光标资源所在的模块句柄,为NULL则使用系统预定义光标 LPCTSTR lpCursorName /光标资源名或系统预定义光标标识名 ) */ wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); /窗口背景为白色 /* GetStockObject():获取已经定义的画笔、画刷、字体等对象的句柄 GetStockObject()函数原型为: HGDIOBJ GetStockObject(int fnObject); /fnObject为对象的标识名 */ wndclass.lpszMenuName=NULL; /窗口中无菜单 wndclass.lpszClassName=lpszClassName; /窗口类名为窗口实例,实例,/-以下是进行窗口类的注册- /* 注册窗口类 Windows系统本身提供部份预定义的窗口类,程序员也可以自定义窗口类,窗口类必须先注册后使用。窗口类的注册由注册函数RegisterClass()实现。其形式为: RegisterClass( /指向一个传递给窗口的参数值的指针*/,实例,/创建窗口操作 HWND hwnd; /窗口结构 hwnd=CreateWindow(lpszClassName, /创建窗口,窗口类名 lpszTitle, /窗口实例的标题名 WS_OVERLAPPEDWINDOW, /窗口的风格 CW_USEDEFAULT,CW_USEDEFAULT, /窗口左上角坐标为缺省值 CW_USEDEFAULT,CW_USEDEFAULT, /窗口的高度和宽度为缺省值 NULL, /此窗口无父窗口 NULL, /此窗口无主菜单 hInstance, /应用程序当前句柄 NULL); /不使用该值 ShowWindow(hwnd,nCmdShow); /显示窗口 UpdateWindow(hwnd); /绘制用户区,实例,/* 消息循环 windows应用程序的运行以消息为核心。windows将产生的消息放入应用程序的消息队列中而应用程序WinMain函数的消息循环提取消息队列中的消息,并将其传递给窗口函数为相应处理过程处理。 MSG msg; /消息结构 while( GetMessage( * /,实例,while( GetMessage( /程序终止时,将信息返回操作系统 ,实例,/-窗口函数- /* 窗口消息处理函数定义了应用程序对接收到的不同消息的响应,它包含了应用程序对各种可用接收到的消息的处理过程,通常 ,窗口函数由一个或多个switch.case语句组成,每一条case语句 对应一种消息,当应用程序接收到一个消息时,相应的case语句被 激活并执行相应的响应程序模块。 窗口函数的一般形式如下: LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); Parameters hwnd :in Handle to the window. uMsg :in Specifies the message. wParam:in Specifies additional message information. The contents of this parameter depend on the value of the uMsg parameter. lParam:in Specifies additional message information. The contents of this parameter depend on the value of the uMsg parameter.,实例,Return Value The return value is the result of the message processing and depends on the message sent. LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) switch (message) case . break; . case WM_DESTROY: /void PostQuitMessage(int nExitCode)函数的作用是向程序发送WMQUIT消息,nExitCode应用程序退出代码 PostQuitMessage(0); /调用该函数发出WM_QUIT消息 default: /缺省消息处理函数,以保证所的发往窗口的消息都能被处理 return DefWindowProc(hwnd,message,wParam,lParam); return (0); */,示例,LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) switch (message) case WM_DESTROY: PostQuitMessage(0); /调用该函数发出WM_QUIT消息 default: /缺省消息处理函数 return DefWindowProc(hwnd,message,wParam,lParam); return (0); /*注: 事件驱动的特点: Windows程序设计围绕着事件或消息的产生驱动产生运行消息处理函数。Windows程序的执行顺序取决于事件发生的顺序,程序的执行是由顺序产生的消息驱动的,程序员可以针对消息类型编写消息处理程序以处理接收的消息,或者发出其他消息以驱动其他处理程序,但是不必预先确定消息的产生顺序。这是面向对象编程中事件驱动的显著特点。 事件驱动编程方法对于编写交互程序很有用处,用这一方法编写的程序使程序避免了死板的操作模式,从而使用户能够按照自己的意愿采用灵活多变的操作模式。 Windows应用程序中的消息传递机制: VC中存在几种系统定义的消息分类,常用的消息由窗口消息、初始化消息、输入消息、系统消息、剪切板消息、文当界面消息、DDE(动态数据交换)消息、应用程序自定义消息等。应用程序
展开阅读全文