您的位置:首页 > 编程语言

windows的窗口消息机制(读windows核心编程笔记):基础篇

2010-07-23 20:38 330 查看
http://blog.csdn.net/zhangyq73/archive/2007/06/19/1658086.aspx

当线程调用函数来建立某个对象时,则该对象就归这个线程的进程所拥有。这样,当进程结束时,如果没有明确删除这个对象,则操作系统会自动删除这个对象。对窗口和挂钩( h o o k )这两种U s e r对象,它们分别由建立窗口和安装挂钩的线程所拥有。
如果一个线程建立一个窗口或安装一个挂钩,线程结束,操作系统会自动删除窗口或卸载挂钩。这意味:建立窗口的线程必须为窗口处理所有消息。这也意味着每个线程,如果至少建立了一个窗口,都由系统对它分配一个消息队列。这个队列用于窗口消息的派送( d i s p a t c h)。为了使窗口接收这些消息,线程必须有它自己的消息循环。

线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配一个T H R E A D I N F O结构,并将这个数据结构与线程联系起来。这个T H R E A D I N F O结构包含一组成员变量,利用这组成员,线程可以认为它是在自己独享的环境中运行。T H R E A D I N F O是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列( send-message queue)、应答消息队列( r e p l y -message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志(wake flag)、以及用来描述线程局部输入状态的若干变量,离开代码(int nExitCode).这个T H R E A D I N F O结构是窗口消息系统的基础.

BOOL PostMessage(
HWND hWnd, // handle of destination window-------Long,接收消息的那个窗口的句柄。
UINT Msg, // message to post----------------------------Long,消息标识符
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
); 作用是消息被放置在线程的登记消息队列中。将一条消息投递到指定窗口的消息队列。投递的消息会在Windows事件处理过程中得到处理。特别适合那些不需要立即处理的窗口消息的发送。
hWnd如设为HWND_BROADCAST,表示投递给系统中的所有顶级窗口。如设为零,表示投递一条线程消息。
当一个线程调用这个函数时,系统要确定是哪一个线程建立了用h w n d参数标识的窗口。然后系统分配一块内存,将这个消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。并且,这个函数还设置Q S _ P O S T M E S S A G E唤醒位。
函数在登记了消息之后立即返回,调用该函数的线程不知道登记的消息是否被指定窗口的窗口过程所处理。实际上,有可能这个指定的窗口永远不会收到登记的消息。如果建立这个特定窗口的线程在处理完它的消息队列中的所有消息之前就结束了,就会发生这种事。

DWORD GetWindowThreadProcessId(
HWND hWnd, // handle to window
LPDWORD lpdwProcessId // address of variable for process identifier
);函数返回线程的I D,这个线程建立了h w n d参数所标识的窗口。线程I D在全系统范围内是唯一的。可以通过对p d w P r o c e s s I d参数传递一个D W O R D地址来获取拥有该线程的进程I D,这个进程I D在全系统范围内也是唯一的。通常,不需要进程I D,只须对这个参数传递N U L L。

BOOL PostThreadMessage(
DWORD idThread, // thread identifier
UINT Msg, // message to post
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);也可以将消息放置在线程的登记消息队列中。PostThreadMessage是一个线程体发送一个消息到指定的线程ID。
当消息被设置到队列中时,M S G结构的h w n d成员将设置成N U L L。当程序要在主消息循环中执行一些特殊处理时要调用这个函数。这个函数既可以发送消息给工作线程,也可以发送给UI线程。
接受PostThreadMessage的线程必须已经有了一个message queue,否则调用PostThreadMessage会失败。因为此原因使用GetLastError会得到错误码为1444,这种情况经常出现,解决方法有如下两种:调用PostThreadMessage,如果失败,就Sleep一段时间再次调PostThreadMessage直到调用成功;创建一个Event对象,让PostThreadMessage等待接受的线程创建一个message queue。可以通过调用PeekMessage强制系统创建一个message queue。
PostThreadMessage传递的消息如果要包含信息,要注意在结束的时候释放消息中的信息。P o s t T h r e a d M e s s a g e在向线程的队列登记了消息之后就立即返回。调用该函数的线程不知道消息是否被处理。

VOID PostQuitMessage( int nExitCode // exit code);为了终止线程的消息循环,应该是由DestroyWindow()中发出的WM_QUIT消息来引起调用的。PostQuitMessage()是posts一个WM_QUIT消息给消息队列并立即返回;进程收到WM_QUIT消息后退出消息循环,程序结束。DestroyWindow()是发送一个WM_DESTROY消息,结束掉此窗口,但是程序并没有结束。
LRESULT SendMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
函数功能是将窗口消息直接发送给一个窗口过程:只有当消息被处理之后, S e n d M e s s a g e才能返回到调用程序。
当一个线程向其他线程所建立的窗口发送消息时, S e n d M e s s a g e的内部工作就复杂得多(即使两个线程在同一进程中也是如此)。当一个线程调用S e n d M e s s a g e向一个由其他进程所建立的窗口发送一个消息,发送线程不可能处理窗口消息,因为发送线程不是运行在接收进程的地址空间中,因此不能访问相应窗口过程的代码和数据。实际上,发送线程要挂起,而由另外的线程处理消息。所以为了向其他线程建立的窗口发送一个窗口消息,系统必须执行下面将讨论的动作。
首先,发送的消息要追加到接收线程的发送消息队列,同时还为这个线程设定Q S _ S E N D M E S S A GE标志。
其次,如果接收线程已经在执行代码并且没有等待消息(如调用G e t M e s s a g e、P e e k M e s s a g e或Wa i t M e s s a g e),发送的消息不会被处理,系统不能中断线程来立即处理消息。
当接收进程在等待消息时,系统首先检查Q S _ S E N D M E S S A G E唤醒标志是否被设定,如果是,系统扫描发送消息队列中消息的列表,并找到第一个发送的消息。几个线程可以同时向一个窗口分别发送消息。这时,系统只是将这些消息追加到接收线程的发送消息队列中。
当接收线程等待消息时,系统从发送消息队列中取出第一个消息并调用适当的窗口过程来处理消息。如果在发送消息队列中再没有消息了,则Q S _ S E N D M E S S A G E唤醒标志被关闭。当接收线程处理消息的时候,调用S e n d M e s s a g e的线程被设置成空闲状态( i d l e),等待一个消息出现在它的应答消息队列中。在发送的消息处理之后,窗口过程的返回值被登记到发送线程的应答消息队列中。发送线程现在被唤醒,取出包含在应答消息队列中的返回值。这个返回值就是调用S e n d M e s s a g e的返回值。这时,发送线程继续正常执行。
当一个线程等待S e n d M e s s a g e返回时,它基本上是处于空闲状态。但它可以执行一个任务:如果系统中另外一个线程向一个窗口发送消息,这个窗口是由这个等待S e n d M e s s a g e返回的线程所建立的,则系统要立即处理发送的消息。在这种情况下,系统不必等待线程去调用G e t M e s s a g e、Peek Message或Wa i t M e s s a g e。
于Wi n d o w s使用上述方法处理线程之间发送的消息,所以有可能造成线程挂起( h a n g)。
S e n d M e s s a g e Ti m e o u t、S e n d M e s s a g e C a l l b a c k、S e n d N o t i f y M e s s a g e和R e p l y M e s s a g e,可以编写保护性代码防止出现这种情况。

LRESULT SendMessageTimeout(
HWND hWnd, // handle of destination window:其窗口程序将接收消息的窗口的句柄
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam, // second message parameter
UINT fuFlags, // how to send the message
UINT uTimeout, // time-out duration
LPDWORD lpdwResult // return value for synchronous call
);函数功能:该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,并且,如果指定的窗口属于不同的线程,直到窗口程序处理完消息或指定的超时周期结束函数才返回。如果接收消息的窗口和当前线程属于同一个队列,窗口程序立即调用,超时值无用。
对fuFlags参数,可以传递值SMTO_NORMAL(定义为0 )、SMTO_ABORTIFHUNG、SMTO_BLOCK、SMTO_NOTIMEOUTIFNOTHUNG或这些标志的组合。
SMTO_ABORTIFHUNG是告诉SendMessageTimeout去查看接收消息的线程是否处于挂起状态,如果是,就立即返回。SMTO_NOTIMEOUTIFNOTHUNG标志使函数在接收消息的线程没有挂起时不考虑等待时间限定值。SMTO_BLOCK标志使调用线程在SendMessageTimeout返回之前,不再处理任何其他发送来的消息。SMTO_NORMAL调用线程等待函数返回时,不被阻止处理其他请求。在Wi n u s e r. h中定义成0,如果不想指定任何其他标志及组合,就使用这个标志。
一个线程在等待发送的消息返回时可以被中断,以便处理另一个发送来的消息。使用S M TO _ B L O C K标志阻止系统允许这种中断。仅当线程在等待处理发送的消息的时候(不能处理别的发送消息),才使用这个标志。使用S M TO _ B L O C K可能会产生死锁情况,直到等待时间期满。例如,如果你的线程向另外一个线程发送一个消息,而这个线程又需要向你的线程发送消息。在这种情况下,两个线程都不能继续执行,并且都将永远挂起。
SendMessageTimeout函数中的uTimeout参数指定等待应答消息时间的毫秒数。如果这个函数执行成功,返回TRUE,消息的结果复制到一个缓冲区中,该缓冲区的地址由pdwResult参数指定。如果函数是由于等待超时而失败,则GetLastError为0(ERROR_SUCCESS)。如果对参数传递了一个无效句柄,GetLastError为1400(ERROR_INVALID_WINDOW_HANDLE)。
如果调用S endMessageTimeout向调用线程所建立的窗口发送一个消息,系统只是调用这个窗口的窗口过程,并将返回值赋给pdwResult。因为所有的处理都必须发生在一个线程里,调用SendMessageTimeout函数之后出现的代码要等消息被处理完之后才能开始执行。
注:如果该消息是一个广播消息,每个窗口可使用全超时周期。例如,如果指定5秒的超时周期,有3个顶层窗回未能处理消息,可以有最多15秒的延迟。

BOOL SendMessageCallback(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam, // second message parameter
SENDASYNCPROC lpResultCallBack,
// function to receive message value
DWORD dwData // value to pass to callback function
);函数功能:该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,并立即返回。当窗口程序处理完消息后,系统调用指定的回调函数,将消息处理的结果和一个应用程序定义的值传给回调函数。
该函数发送消息到接收线程的发送消息队列,并立即返回使发送线程可以继续执行。当接收线程完成对消息的处理时,一个消息被登记到发送线程的应答消息队列中。然后,系统通过调用一个函数将这个应答通知给发送线程,该函数是使用下面的原型编写的。
参数 hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息指定信息。
IParam:指定附加的消息指定信息。
IpResultCallBack:指向回收函数的指针,窗曰程序处理完消息后调用该回调函数。参见SendAsyncProc可得到合适的回调函数的信息。如果hwnd为HWND_BROADCAST,系统为每个顶层窗口调用一次SendASyncProc回调函数。
dwData:一个应用程序定义的值,被传给由参数IPResultCallBack指向的回调函数。
返回值:如果函数调用成功,返回非零值。如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用GetLastError函数
备注:如果发送一个低于WM_USER范围的消息给异步消息函(PostMessage,SendNotifyMesssge;SendMessageCallback),消息参数不能包含指针。否则,操作将会失败。函数将在接收线程处理消息之前返回,发送者将在内存被使用之前释放。 需要以HWND_BROADCAST方式通信的应用程序应当用函数RegisterWindwosMessage来获得应用程序间通信的独特的消息。 此回调函数仅当调用SendMessagecallback的线程调用GetMessage,PeekMessage或WaitMessage时调用。
该函数发送消息到接收线程的发送消息队列,并立即返回使发送线程可以继续执行。当接收线程完成对消息的处理时,一个消息被登记到发送线程的应答消息队列中。然后,系统通过调用一个函数将这个应答通知给发送线程,该函数是使用下面的原型编写的。
VOID CALLBACK ResultCallBack(
HWND hwnd,
UINT uMsg,
ULONG_PTR dwData,
LRESULT lResult);
必须将这个函数的地址传递给S e n d M e s s a g e Callback函数作为pfnResultCallbac k参数值。当调用这个函数时,要把完成消息处理的窗口的句柄传递到第一个参数,将消息值传递给第二个参数。第三个参数dwData,总是取传递到SendMessageCallback函数的dwData参数的值。系统只是取出对SendMessageCallback函数指定的参数值,再直接传递到ResultCallback函数。ResultCallback函数的最后一个参数是处理消息的窗口过程返回的结果。因为SendMessageCallback在执行线程间发送时会立即返回,所以在接收线程完成对消息的处理时不是立即调用这个回调函数。而是由接收线程先将一个消息登记到发送线程的应答消息队列。发送线程在下一次调用GetMessage、PeekMessage、WaitMessage或某个SendMessage*函数时,消息从应答消息队列中取出,并执行ResultCallBack函数。
SendMessageCallback函数还有另外一个用处。Windows提供了一种广播消息的方法,用这种方法你可以向系统中所有现存的重叠(overlapped)窗口广播一个消息。这可以通过调用SendMessage函数,对参数hwnd传递HWND_BROADCAST(定义为-1)。使用这种方法广播的消息,其返回值我们并不感兴趣,因为SendMessage函数只能返回一个LR ESULT。但使用SendMessageCallback,就可以向每一个重叠窗口广播消息,并查看每一个返回结果。对每一个处理消息的窗口的返回结果都要调用ResultCallback函数。
如果调用SendMessageCallback向一个由调用线程所建立的窗口发送一个消息,系统立即调用窗口过程,并且在消息被处理之后,系统调用ResultCallBack函数。在ResultCallBack函数返回之后,系统从调用SendMessageCallback的后面的代码行开始执行。

BOOL SendNotifyMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
函数功能:该函数将指定的消息发送到一个窗口。如果该窗口是由调用线程创建的;此函数为该窗口调用窗口程序,并等待窗口程序处理完消息后再返回。如果该窗口是由不同的线程创建的,此函数将消息传给该窗口程序,并立即返回,不等待窗口程序处理完消息。
参数:hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。
Msg:指定被发送的消息。
wParam:指定附加的消息指定信息。
IParam:指定附加的消息指定信息。
返回值:如果函数调用成功,返回非零值;如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用GetLastError函数。
备注:如果发送一个低于WM_USER范围的消息给异步消息函数(PostMessage,SendNotifyMessage,SendMesssgeCallback),消息参数不能包含指针。否则,操作将会失败。函数将在接收线程处理消息之前返回,发送者将在内存被使用之前释放。 需要以HWND_BROADCAST方式通信的应用程序应当用函数RegisterWindwosMessage来获得应用程序间通信的独特的消息。
SendNotifyMessage和SendNotifyMessage的区别:
相同点:一个消息置于接收线程的发送消息队列中,并立即返回到调用线程。
不同点:首先,SendNotifyMessage是向另外的线程所建立的窗口发送消息,发送的消息比起接收线程消息队列中存放的登记消息有更高的优先级。换句话说,由SendNotifyMessage函数存放在队列中的消息总是在PostMessage函数登记到队列中的消息之前取出。
其次,当向一个由调用进程建立的窗口发送消息时,SendNotifyMessage同SendMessage函数完全一样:SendNotifyMessage在消息被处理完之后才能返回。

注意:发送给窗口的大多数消息是用于通知的目的。也就是,发送消息是因为窗口需要知道某个状态已经发生变化,在程序能够继续执行之前,窗口要做某种处理。例如,WM_ACTIVATE、WM_DESTROY、WM_ENABLE、WM_SIZE、WM_SETFOCUS和WM_MOVE等都是系统发送给窗口的通知,而不是登记的消息。这些消息是系统对窗口的通知,因此系统不需要停止运行以等待窗口过程处理这些消息。与此相对应,如果系统向一个窗口发送一个WM_CREATE消息,则在窗口处理完这个消息之前,系统必须等待。如果返回值是-1,则不再建立窗口。

BOOL ReplyMessage(LRESULT lResult // message-specific reply );
函数功能:该函数用于应答由函数SendMessage发送的消息,不返回控制给调用SendMessage的函数。它是要告诉系统,为了知道消息结果,它已经完成了足够的工作,结果应该包装起来并登记到发送线程的应答消息队列中。这将使发送线程醒来,获得结果,并继续执行。
IResult:指定消息处理的结果。可能的值由所发送的消息确定。在调用ReplyMessage之后,发送消息的线程恢复执行,而处理消息的线程继续处理消息。两个线程都不会被挂起,都可以正常执行。当处理消息的线程从它的窗口过程返回时,它返回的任何值都被忽略。
返回值:如果调用线程正处理从其他线程或进程发送的消息,返回非零值。如果调用线程不是正处理从其他线程或进程发送的消息,返回值是零。
备注:调用此函数,接收消息的窗口程序允许调用SendMessage的线程继续运行,尽管接收消息的线程已返回控制。调用ReplyMessage的线程也继续运行。 如果消息不是通过SendMessage发送的,或者消息由同一个线程发送,ReplyMessage不起作用。

BOOL InSendMessage(VOID);这个函数在线程处理线程间发送的消息时,返回TRUE,而在线程处理线程内发送的或登记的消息时,返回FALSE。InSendMessage和ReplyMessage的返回值是一样的。

DWORD InSendMessageEx(
LPVOID lpReserved // must be NULL
);
这个函数的返回值指出正在处理的消息的类型。如果返回值是ISMEX_NOSEND(定义为0),表示线程正在处理一个线程内发送的或登记的消息。如果返回值不是ISMEX_NOSEND,就是表中描述的位标志的组合。

标志 描述
I S M E X _ S E N D 线程在处理一个线程间发送的消息,该消息是用S e n d M e s s a g e或Send Message Ti m e o u t函数发送的。如果没有设定I S M E X _ R E P L I E D标志,发送线程被阻塞,等待应答
I S M E X _ N O T I F Y 线程在处理一个线程间发送的消息,该消息是用SendNotifyM e s s a g e函数发送的。发送线程不等待应答,也不会阻塞
I S M E X _ C A L L B A C K 线程在处理线程间发送的消息,该消息是用S e n d M e s s a g eC a l l b a c k发送的。发送线程不等待应答,也不会被阻塞
I S M E X _ R E P L I E D 线程在处理线程间发送的消息,并已经调用R e p l y M e s s a g e。发送线程不会被阻塞

唤醒一个线程:
当一个线程调用GetMessage或WaitMessage,但没有对这个线程或这个线程所建立窗口的消息时,系统可以挂起这个线程,这样就不再给它分配CPU时间。当有一个消息登记或发送到这个线程,系统要设置一个唤醒标志,指出现在要给这个线程分配CPU时间,以便处理消息。正常情况下,如果用户不按键或移动鼠标,就没有消息发送给任何窗口。这意味着系统中大多数线程没有被分配给CPU时间。

队列状态标志:
DWORD GetQueueStatus(
UINT flags // queue-status flags
);函数功能:查询队列的状态:参数fuFlags是一个标志或一组由OR连接起来的标志,可用来测试特定的唤醒位。下表给出了各个标志取值及含义。
标志 队列中的消息
Q S _ K E Y W M _ K E Y U P、W M _ K E Y D O W N、W M _ S Y S K E Y U P或W M _ S Y S K E Y D O W N
Q S _ M O U S E M O V E W M _ M O U S E M O V E
Q S _ M O U S E B U T TO N W M _ ? B U T TO N *(其中?代表L、M或R、*代表D O W N、U P或DBLCLK )
Q S _ M O U S E 同Q S _ M O U S E M O V E | Q S _ M O U S E B U T TO N
Q S _ I N P U T 同Q S _ M O U S E | Q S _ K E Y
Q S _ PA I N T W M _ PA I N T
Q S _ T I M E R W M _ T I M E R
Q S _ H O T K E Y W M _ H O T K E Y
Q S _ P O S T M E S S A G E 登记的消息(不同于硬件输入事件)。当队列在期望的消息过滤器范围内没有登记的消息时,这个标志要消除。除此之外,这个标志与Q S _ A L L P O S T M E S S A G E相同
Q S _ A L L P O S T M E S S A G E 登记的消息(不同于硬件输入事件)。当队列完全没有登记的消息时(在任何消息过滤器范围),该标志被清除。除此之外,该标志与Q S _ P O S T M E S S A G E相同
Q S _ A L L E V E N T S 同Q S _ I N P U T | Q S _ P O S T M E S S A G E | Q S _ T I M E R | Q S _ PA I N T | Q S _ H O T K E Y
Q S _ Q U I T 已调用P o s t Q u i t M e s s a g e。注意这个标志没有公开,所以在Wi n U s e r.h 文件中没有。它由系统在内部使用
Q S _ S E N D M E S S A G E 由另一个线程发送的消息
Q S _ A L L I N P U T 同QS_ALLEVENTS|QS_SENDMESSAGE

当调用GetQueueStatus函数时,fuFlags将队列中要检查的消息的类型告诉GetQueueStatus。用OR连接的QS_*标识符的数量越少,调用执行的就越快。当GetQueueStatus返回时,线程的队列中当前消息的类型在返回值的高字(两字节)中。返回的低位字指示自从上一次调用GetQueueStatus,GetMessage或PeekMessage以来加在队列后并且未被处理的消息的类型。
不是所有的唤醒标志都由系统平等对待。对于QS_MOUSEMOVE标志,只要队列中存在一个未处理的WM_MOUSEMOVE消息,这个标志就要被设置。当GetMessage或PeekMessage (用PM_REMOVE)从队列中放入新的WM_MOUSEMOVE消息之前,这个标志被关闭。QS_KEY、QS_MOUSEBUTTON和QS_HOTKEY 标志都根据相应的消息按与此相同的方式处理。
Q S_PAINT标志的处理与此不同。如果线程建立的一个窗口有无效的区域,QS_PAINT标志被设置。当这个线程建立的所有窗口所占据的区域变成无效时(通常由于对ValidateRect、Valid ateRegion或BeginPaint的调用而引起),QS_PAINT标志就被关闭。只有当线程建立的所有窗口都无效时,这个标志才关闭。调用GetMessage或PeekMessage对这个唤醒标志没有影响。
当线程的登记消息队列中至少有一个消息时,QS_POSTMESSAGE标志就被设置。这不包括线程的虚拟输入队列中的硬件事件消息。当线程的登记消息队列中的所有消息都已经处理,队列变空时,这个标志被复位。
每当一个定时器(由线程所建立)报时(go off),QS_TIMER标志就被设置。在Get Message或P eekMessage返回WM_TIMER事件之后,QS_TIMER标志被复位,直到定时器再次报时。
Q S _ SENDMESSAGE标志指出有一个消息在线程的发送消息队中。系统在内部使用这个标志,用来确认和处理线程之间发送的消息。对于一个线程向自身发送的消息,不设置这个标志。虽然可以使用QS_SENDMESSAGE标志,但很少需要这样做。
还有一个未公开的队列状态标志QS_QUIT。当一个线程调用PostQuitMessage时, Q S_ QUIT标志就被设置。系统并不实际向线程的消息队列追加一个WM_QUIT消息。GetQueu eStatus函数也不返回这个标志的状态。

从线程的队列中提取消息的算法:当一个线程调用G e t M e s s a g e或P e e k M e s s a g e时,系统必须检查线程的队列状态标志的情况,并确定应该处理哪个消息。
1) 如果QS_SENDMESSAGE标志被设置,系统向相应的窗口过程发送消息。GetMessage或PeekMessage函数在内部进行这种处理,并且在窗口过程处理完消息之后不返回到线程,这些函数要等待其他要处理的消息。
2) 如果消息在线程的登记消息队列中,函数GetMessage或PeekMessage填充传递给它们的MSG结构,然后函数返回。这时,线程的消息循环通常调用DispatchMessage,让相应的窗口过程来处理消息。
3) 如果Q S _ Q U I T标志被设置。G e t M e s s a g e或P e e k M e s s a g e返回一个W M _ Q U I T消息(其中w P a r a m参数是规定的退出代码)并复位Q S _ Q U I T标志。
4) 如果消息在线程的虚拟输入队列,函数GetMessage或PeekMessage返回硬件输入消息。
5) 如果Q S _ PA I N T标志被设置,GetMessage或PeekMessage为相应的窗口返回一个WM-PAINT消息。
6) 如果QS_TIMER标志被设置,GetMessage或PeekMessage返回一个WM_TIMER消息。
微软在设计这个算法时有一个大前提,就是应用程序应该是用户驱动的,用户通过建立硬件输入事件(键盘和鼠标操作)来驱动应用程序。在使用应用程序时,用户可能按一个鼠标按钮,引起一系列要发生的事件。应用程序通过向线程的消息队列中登记消息使每个个别的事件发生。
所以如果按鼠标按钮,处理W M _ L B U T TO N D O W N消息的窗口可能向不同的窗口投送三个消息。由于是硬件事件引发三个软件事件,所以系统要在读取用户的下一个硬件事件之前,先处理这些软件事件。这也说明了为什么登记消息队列要在虚拟输入队列之前检查。

BOOL TranslateMessage(
CONST MSG *lpMsg // address of structure with message
);这个函数检查是否有一个WM_KEYDOWN或一个WM_ SYSKEYDOWN消息从输入队列中取出。如果有一个这样的消息被取出,系统检查虚键(virtual key)信息是否能转换成等价的字符。如果虚键信息能够转换,TranslateMessage调用PostMessage将一个WM_CH AR消息或一个WM_SYSCHAR消息放置在登记消息队列中。下次调用GetMessage时,系统首先检查登记消息队列中的内容,如果其中有消息存在,从队列中取出消息并将其返回。返回的消息将是WM_CHAR消息或WM_SYSCHAR消息。再下一次调用GetMessage时,系统检查登记消息队列,发现队列已空。系统再检查输入队列,在其中找到WM_(SYS)KEYUP消息。GetMessage返回这个消息。
下面的硬件事件序列W M _ K E Y D O W N、W M _ K E Y U P生成下面的到窗口过程的消息序列(假定虚键信息可以转换成等价的字符):WM_KEYDOWN WM_CHAR WM_KEYUP。
在系统检查了登记消息队列之后,但尚未检查虚拟输入队列时,它要检查Q S _ Q U I T标志。我们知道,当线程调用P o s t Q u i t M e s s a g e时设置Q S _ Q U I T标志。调用P o s t Q u i t M e s s a g e类似于(但不相同)调用P o s t T h r e a d M e s s a g e。P o s t T h r e a d M e s s a g e将消息放置在消息队列的尾端,并使消息在检查输入队列之前被处理。为什么P o s t Q u i t M e s s a g e设置一个标志,而不是将W M _ Q U I T消息放入消息队列中?有两个理由。第一,在低内存(low memory)情况下,登记一个消息有可能失败。如果一个程序想退出,它应该被允许退出,即使是在低内存的情况下。第二个理由是使用标志可使线程在线程的消息循环结束前完成对所有其他登记消息的处理。
注意要记住G e t M e s s a g e或P e e k M e s s a g e函数只检查唤醒标志和调用线程的消息队列。这意味着一个线程不能从与其他线程挂接的队列中取得消息,包括同一进程内其他线程的消息。

利用内核对象或队列状态标志唤醒线程:G e t M e s s a g e或P e e k M e s s a g e函数导致一个线程睡眠,直到该线程需要处理一个与用户界面(UI)相关的任务。一个线程可以调用M s g Wa i t F o r M u l t i p l e O b j e c t s或M s g Wa i t F o r M u l t i p l e O b j e c t s E x函数,使线程等待它自已的消息。函数原型:
DWORD MsgWaitForMultipleObjects(
DWORD nCount, // number of handles in the handle array LPHANDLE pHandles, // pointer to the object-handle array
BOOL fWaitAll, // wait for all or wait for one
DWORD dwMilliseconds, // time-out interval in milliseconds
DWORD dwWakeMask // type of input events to wait for
);
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount, // number of handles in handle array
LPHANDLE pHandles, // pointer to an object-handle array
DWORD dwMilliseconds, // time-out interval in milliseconds
DWORD dwWakeMask, // type of input events to wait for
DWORD dwFlags // wait flags
);
当一个内核对象变成有信号状态( s i g n a l e d)或当一个窗口消息需要派送到调用线程建立的窗口时,这两个函数用于线程调度。在内部,系统只是向内核句柄的数组添加一个事件内核对象。d w Wa k e M a s k参数告诉系统何时让事件成为有信号状态。d w Wa k e M a s k参数的可能取值的合法范围与可传递到G e t Q u e u eS t a t u s函数的参数值一样。
正常情况下,当Wa i t F o r M u l t i p l e O b j e c t s函数返回时,它返回变成有信号状态的对象的索引以满足调用(WA I T _ O B J E C T _ O到WA I T _ O B J E C T _ O + n C o u n t-1)。增加d w Wa k e M a s k参数就如同向调用增加又一个句柄。如果由于唤醒掩码, M s g Wa i t F o r M u l t i p l e O b j e c t s ( E x )被满足,返回值将是WA I T _ O B J E C T _ O+n C o u n t。
M s g Wa i t F o rM u l t i p l e O b j e c t s E x是M s g Wa i t F o r M u l t i p l e O b j e c t s的一个超集( s u p e r s e t)。新的特性是通过d w F l a g s参数引进的。对这个参数,可以指定下面标志的任意组合。
标志 描述
M W M O _ WA I TA L L 函数等待所有要变成有信号状态的内核对象及要出现在线程队列中的特定消息。如果没有这个标志,函数等待直到有一个内核对象变成s i g n a l e d,或指定的消息出现在线程的队列中
M W M O _ A L E RTA B L E 函数在一个可报警状态等待
M W M O _ I N P U TAVA I L A B L E 当任何指定的消息在线程的队列中时
如果不想要任何这些附加的特性,可对参数d w F l a g s传递零(0)。
备注:由于这个函数只是向内核句柄的数组增加一个内部事件内核对象, n C o u n t参数的最大值是M A X I M U M _ WA I T _ O B J E C T减1或6 3。
对f Wa i t A l l参数传递FA L S E时,那么当一个内核对象是有信号的( s i g n a l e d),或当指定的消息类型出现在线程的队列时,函数返回。
当对f Wa i t A l l参数传递T R U E时,那么当所有内核对象成为有信号状态,并且指定的消息类型出现在线程的队列中时,函数返回。
当调用这两个函数时,实际是查看是否有指定类型的新消息被放入调用线程的队列。

通过消息发送数据:W M _ S E T T E X T消息的处理。当调用S e n d M e s s a g e时,函数中的代码要检查是否要发送一个W M _ S E T T E X T消息。如果是,就将以零结尾的字符串从调用进程的地址空间放入到一个内存映像文件中,该内存映像文件可在进程间共享。然后再发送消息到其他进程的线程。当接收线程已准备好处理W M _ S E T T E X T消息时,它在自己的地址空间中确定包含新的窗口文本标题的共享内存映像文件的位置,再将W M _ S E T T E X T消息派送到相应的窗口过程。在处理完消息之后,内存映像文件被删除。幸而大多数消息不要求这种类型的处理。仅当这种消息是程序在进程间发送的消息,特别是消息的w P a r a m或l P a r a m参数表示一个指向数据结构的指针时,要做这样的处理。
关于W M _ G E T T E X T消息的处理:char szBuf[200];SendMessage(FindWindow(NULL, "Calculator"), WM_GETTEXT, sizeof(szBuf), (LPARAM) szBuf);W M _ G E T T E X T消息请求C a l c u l a t o r的窗口过程用该窗口的标题填充s z B u f所指定的缓冲区。当一个进程向另一个进程的窗口发送这个消息时,系统实际上必须发送两个消息。首先,系统要向那个窗口发送一个W M _ G E T T E X T L E N G T H消息。窗口过程通过返回窗口标题的字符数来响应这个消息。系统利用这个数字来建立一个内存映像文件,用于在两个进程之间共享。当内存映像文件被建立时,系统就发送消息来填充它。然后系统再转回到最初调用S e n d M e s s a g e的进程,从共享内存映像文件中将数据复制到s z B u f所指定的缓冲区中,然后从S e n d M e s s a g e调用返回。
要建立自己的(W M _ U S E R+x)消息,并从一个进程向另一个进程的窗口发送,那又会怎么样?为此,微软建立了一个特殊的窗口消息,W M _ C O P Y D ATA以解决这个问题。COPYDATASTRUCT cds;
SendMessage(hwndReceiver, WM_COPYDATA,
(WPARAM)hwndSender, (LPARAM) &cds);C O P Y D ATA S T R U C T是一个结构,定义在Wi n U s e r. h文件中,形式如下面的样子:
typedef struct tagCOPYDATASTRUCT
{
ULONG_PTR dwData;//是一个备用的数据项,可以存放任何值。
DWORD cbData;//规定了向另外的进程发送的字节数
PVOID lpData;//指向要发送的第一个字节,所指向的地址,当然在发送进程的地址空间中。
} COPYDATASTRUCT:当一个进程要向另一个进程的窗口发送一些数据时,必须先初始化C O P Y D ATA S T R U C T结构。当S e n d M e s s a g e看到要发送一个W M _ C O P Y D ATA消息时,它建立一个内存映像文件,大小是c b D a t a字节,并从发送进程的地址空间中向这个内存映像文件中复制数据。然后再向目的窗口发送消息。在接收消息的窗口过程处理这个消息时, l P a r a m参数指向已在接收进程地址空间的一个C O P Y D ATA S T R U C T结构。这个结构的l p D a t a成员指向接收进程地址空间中的共享内存映像文件的视图。
关于W M _ C O P Y D ATA消息,应该注意三个重要问题:
只能发送这个消息,不能登记这个消息。不能登记一个W M _ C O P Y D ATA消息,因为在接收消息的窗口过程处理完消息之后,系统必须释放内存映像文件。如果登记这个消息,系统不知道这个消息何时被处理,所以也不能释放复制的内存块。
系统从另外的进程的地址空间中复制数据要花费一些时间。所以不应该让发送程序中运行的其他线程修改这个内存块,直到S e n d M e s s a g e调用返回。
用W M _ C O P Y D ATA消息,可以实现1 6位和3 2位之间的通信。它也能实现3 2位与6 4位之间的通信。这是使新程序同旧程序交流的便捷方法。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhangyq73/archive/2007/06/19/1658086.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: