《消息与消息队列.ppt》由会员分享,可在线阅读,更多相关《消息与消息队列.ppt(13页珍藏版)》请在三一办公上搜索。
1、第二章 消息与消息队列,与传统的应用程序不同,Windows应用程序并不显式地用一个函数的调用来(如getchar)或的输入,而是等待Windows系统把输入传给它们。Windows系统把应用程序的所有输入传入给应用程序窗口,每一个窗口都有一个称作窗口过程的函数,当窗口有输入时Windows系统要调用它,窗口过程处理输入并把控制返回给Windows。Windows系统以消息的形式把输入传给窗口过程,消息是由Windows系统或应用程序产生的,Windows系统对于每一个输入事件都要产生消息-例如键盘、鼠标的动作。Windows系统为了响应应用程序给系统带来的变化也会产生消息-例如应用程序改变了
2、系统字体或改变了一个窗口的大小。应用程序可以通过产生消息指导它自己的窗口来完成某项任务,或是与其他应用程序的窗口进行通信。消息有六个参数:typedef struct tagMSG HWND hwnd;UINT message;WPARAM wParam;LPARAM lParam;DWORD time;POINT pt;MSG;,目的窗口(hwnd):,接收消息的窗口,也就是那一个窗口的窗口过程将被调用。,消息标志(message):,一个命名的常量,用来标志消息的目的,也就是消息的含义。如果窗口过程收到一条消息,它就通过消息标志来决定如何处理这条消息。,消息的附加内容,具体的含义取决于具体
3、消息标志message。参数分为高低位。消息参数可以是一个整数、紧缩的位标志、一个含有附加数据结构的指针等等。,参数1(wParam):,参数2(lParam):,消息的附加内容,具体的含义取决于具体消息标志message。参数分为高低位。消息参数可以是一个整数、紧缩的位标志、一个含有附加数据结构的指针等等。,消息发出的时间。,分发时间(time):,光标位置(pt):,消息发生时鼠标光标的位置。以设备坐标系定位。,排队消息:,Windows系统在同一时刻可以显示多个窗口,要发送鼠标和键盘输入到相应的窗口,Windows要用到消息队列,它要管理一个系统消息队列和任意数目的线程消息队列,每一个队
4、列对应一个线程。无论什么时候,只要用户移动鼠标或敲键盘,鼠标或键盘的设备驱动程序都要把输入转换成消息,并把它们放到系统消息队列中。Windows系统从系统,构造消息参数的宏:MAKELPARAM、MAKEWPARAM,析取消息参数的宏:HIWORD、LOWORD,消息路由(route):,消息路由的含义是消息在系统中传播的途径。Windows系统用两种方式向窗口过程发消息:把消息投递到一个先进先出的消息队列中(线程消息队列),它是一个系统定义的内存块用于临时存储消息;或是把消息直接发给窗口过程。投递到消息队列中的消息叫做排队消息,主要是用户通过鼠标或键盘的输入结果,如WM_MOUSEMOVE,
5、WM_LBUTTONDOWN,WM_KEYDOWN,WM_CHAR等。其它的排队消息包括定时器、绘制和退出消息:WM_TIMER,WM_PAINT,WM_QUIT。所有直接发送到窗口过程的其它消息称作非排队消息。,消息队列中每次移走一条消息,确定目的窗口,再把它投递到创建目的窗口的线程的消息队列中,线程消息队列从它的接收所有由该线程创建的窗口的鼠标和键盘消息。线程从它的队列中移走消息并指导Windows系统将它们发送到相应的窗口过程进行处理。比较特别的是WM_PAINT消息,Windows系统总是把这条消息放在消息队列的最后,这样可以保证窗口按先进先出次序接收它的输入消息,WM_PAINT消息
6、被保留在消息队列中,直到队列中没有其他消息时才发送到窗口过程。同一个窗口的多个WM_PAINT消息被合并成一个,并把客户区的所有无效部分合并成一个区域,合并WM_PAINT消息节约了窗口必须重画客户区内容的时间。,非排队消息:,非排队消息直接发送到目标窗口过程,不通过系统消息队列和线程消息队列。Windows系统一般通过发送非排队消息把影响某窗口的事件通知窗口。例如,如果用户激活一个新的应用程序窗口,Windows就会向该窗口发送一系列的消息。非排队消息也有可能发生在应用程序调用一个Windows系统函数时,例如在应用程序调用函数SetWindowPos来移动一个窗口后,Windows系统发送
7、一条WM_WINDOWPOSCHANGED消息。应用程序使用SendMessage,SendNotifyMessage,SendDlgItemMessage来发送消息的。,消息环:,应用程序必须删除和处理投递到它的线程消息队列中的消息,单一线程的应用程序一般是在它的WinMain函数中使用一个消息环来删除消息,并把消息发送到相应的窗口过程进行处理。具有多线程的应用程序在创建窗口的每一个线程中使用一个消息环。while(GetMessage(,一个简单的消息环具有对下列函数的调用:GetMessage,TranslateMessage和DispatchMessage。函数GetMessage从队
8、列中检取一条消息并把它父直到一个MSG结构中,GetMessage通常返回TRUE,除非它收到一个WM_QUIT消息。如果某个线程想接收键盘的字符输入,那么线程消息环中就必须含有函数TranslateMessage。Windows系统在用户每按一次键时会产生一个虚键,消息(WM_KEYDOWN和WM_KEYUP),虚键消息含有一个标志哪一个键被按过的虚键码,但不是它的字符值,要得到这个值,就必须调用函数TranslateMessage,由它来把虚键消息翻译成字符消息(WM_CHAR),再把它放回到消息队列中,这样字符消息才能在消息环的下轮循环中被发送到窗口过程。,函数DispatchMessa
9、ge把消息发送到与MSG结构中指定的窗口句柄相应的窗口过程,如果窗口句柄是HWND_TOPMOST,DispatchMessage就把消息发送到系统中所有顶层窗口窗口过程。如果是NULL,则什么也不做。,应用程序的主线程在初始化应用程序并且至少创建了一个窗口之后就开始了消息循环,一旦开始,消息环就连续不断的从线程的消息队列中检取消息柄把他们分发到相应的窗口,函数GetMessage从消息队列中检取到WM_QUIT消息后,消息环就结束了。,一个消息队列只需要有一个消息环,而不管应用程序有多少个窗口,因为DispatchMessage总是能把消息发送到相应的窗口。,应用程序可以有多种方法修改它的消
10、息环,例如它可以从队列中检取消息但并不投递到任何窗口,这对投递那些不指定窗口的消息的应用程序是很有用的(这些消息含有NULL窗口句柄,是给应用程序的,而不是给任何指定窗口的)。应用程序也可以指导GetMessage来搜索消息队列中的特定消息,而不理会其他消息,这对那些有时不安消息队列先进先出次序检取消息的应用程序是很有用的。,图2-1 排队消息路由图,投递和发送消息:,任何应用程序都能投递和发送消息,就像系统一样。应用程序投递一条消息是通过把它复制到消息队列,发送消息则是通过把消息数据作为窗口过程的参数直接调用窗口过程。要投递和发送消息,用到PostMessage和SendMessage函数。
11、应用程序一般是通过发送一条消息通知窗口过程立即完成某项任务,函数SendMessage把消息发送到指定窗口的窗口过程,并等待窗口过程返回。有时,应用程序也有可能要求向系统中的所有顶层窗口发送或投递一条消息,例如改变系统时间。这时以HWND_TOPMOST句柄来发送,系统回自动把这条消息分发到每个顶层窗口的消息队列中(或调用每个窗口的窗口过程)。应用程序能够投递一条消息而不指定窗口。这条消息被发送到当前线程消息队列中。应用程序必须消息环中处理这类消息,此类消息适用于整个应用程序,而不是某一个窗口。使用函数InSendMessage能够判断要处理的消息是不是从另外一个线程发来的。这种能力在需要根据
12、消息的来源进行处理时是非常有用的。PostMessage并不是总能成功,当消息队列满时函数会失败,因此需要判断其返回值以确定是否投递成功。,消息种类(根据消息定义者):,1.系统消息:系统使用系统定义的消息来控制应用程序的操作,并给应用程序提供输入或其它信息进行处理。系统在与应用程序进行通信时是发送或投递系统消息,应用程序也能投递或发送系统消息,应用程序通常用这些消息来控制窗口类创建的控制窗口的操作。每条系统消息都有一个为一的消息标志,对应于一个符号常量,它表明了消息的目的。例如WM_PAINT。消息的前缀表明了消息的类别:,表2-1 消息类别,通用窗口消息覆盖了一个较大范围的信息和请求,包括
13、鼠标和键盘输入消息、菜单和对话框输入消息、窗口创建和管理消息以及动态数据交换消息(DDE)。,2.应用程序自定义消息:应用程序可以创建用在它自己的窗口中的消息,或是与其它进程中的窗口进行通信的消息。如果应用程序创建了自己的消息,接受它们的窗口过程必须能够对消息进行翻译,并提供相应处理(因为系统的缺省消息处理函数DefWindowProc不能处理用户自定义消息)。Windows系统保留用于系统定义的消息标志值的范围从0 x0000到0 x3fff(WM_USER-1)和0 x8000到0 xbfff,应用程序不能把这些值用于私有消息。从0 x0400(WM_USER)到0 x7fff间的值用于应
14、用程序定义的用于它自己的消息标志,而从0 xc000到0 xffff间的值是应用程序为了与其它应用程序中的窗口进行通信所定义的消息标志。应用程序使用RegisterWindowMessage注册一条消息时,Windows系统返回的消息标志在0 xc000到0 xffff间,这个函数返回的消息标志在系统中是唯一的。如果应用程序要创建与其它应用程序中的窗口进行通信的消息,这需要使用RegisterWindowMessage来对它进行注册,这个函数可以防止由于其它的应用程序基于不同的目的使用了相同的消息标志所产生的冲突。,例子:本应用程序自己用的消息定义:#define USR_MYMESSAGE1
15、 WM_USER+1#define USR_MYMESSAGE2 WM_USER+2应用程序间通讯用的消息:应用程序1:UINT app1MyMessage1=RegisterWindowMessage(“USR_MYMESSAGE1”);UINT app1MyMessage2=RegisterWindowMessage(“USR_MYMESSAGE2”);应用程序2:UINT app2MyMessage1=RegisterWindowMessage(“USR_MYMESSAGE1”);UINT app2MyMessage2=RegisterWindowMessage(“USR_MYMESSA
16、GE2”);其结果应该是:app1MyMessage1=app2MyMessage1;app1MyMessage2=app2MyMessage2并且都在0 xc000到0 xffff间。通常消息注册应放在应用程序初始化部分。注册完,两个应用程序就可以相互发送消息通信了。,消息过滤:,应用程序可以使用函数GetMessage或PeekMessage来指定一个消息过滤器,从消息队列中检取指定的消息(忽略其它的消息),这可以是一个消息标志的范围、一个窗口句柄或者两者都是。GetMessage和PeekMessage利用消息过滤器有效地检取队列中的某条消息。如果某个应用程序必须检索队列中排在后面的消息
17、,消息过滤是很有用的。过滤消息的应用程序必须保证满足消息过滤器的消息是能够被投递的。例如,如果某个应用程序的消息过滤器用于一个并不接收键盘输入的窗口中的WM_CHAR消息,GetMessage就不能返回,这样会“挂起”应用程序。,消息死锁:,调用函数SendMessage的线程向另一个线程发送一条消息,要等待接收消息的窗口过程返回,如果接收消息的线程在处理时放弃了控制,发送消息的线程就不能继续执行下去,因为它正等待SendMessage返回,这种情况就叫做死锁。接收消息的线程无需直接放弃控制,下列函数任意一个都可以让线程放弃控制。DialogBox GetMessage DialogBoxIndirect MessageBox DialogBoxIndirectParam PeekMessage DialogBoxParam,窗口过程可以通过调用函数InSendMessage来确定它所接收的消息是不是另一个线程发过来的。在调用任何可能使线程放弃控制的函数前,窗口过程应当调用ReplyMessage来回复发送线程,是的发送线程得以继续执行。,