第27章 窗口与消息(1)

27.1 线程的消息队列

(1)Windows用户对象(User Object)

  ①类型:图标、光标、窗口类、菜单、加速键表等

  ②当一个线程创建某个对象时,则该对象归这个线程的进程所有,当进程结束时,如果用户没有明确删除这个对象,则操作系统会自动删除这个对象。

  ③窗口和钩子(hook)这两种用户对象,它们分别由建立窗口和安装钩子的线程所拥有(注意,不是进程)。如果一个线程建立一个窗口或安装一个钩子。然后线程结束,操作系统会自动删除窗口或卸载钩子。

  ④窗口及线程的拥有关系使得建立窗口的线程必须为它的窗口处理所有消息。这意味着如果线程建立了一个窗口(或调用一个与图形用户界面有关的函数),系统将对它分配一个消息队列,用来向窗口派送(dispatch)消息。

(2)THREADINFO的内部数据结构(注意它与某一线程相关联)

27.2 将消息投递到线程的消息队列

27.2.1 BOOL PostMessage(hwnd,uMsg,wParam,lParam);

(1)调用该函数时,系统先确定是哪一个线程建立了hwnd窗口,然后分配一块内存,将消息参数存储在这块内存,然后把这块内存增加到对应线程的Posted-Message队列中。

(2)添加到队列后,PostMessage还设置了Wake Flags变量的QS_POSTMESSAGE唤醒位。

(3)消息投递完会函数立即返回。

27.2.2 BOOL PostThreadMessage(dwThreadId,uMsg,wParam,lParam)

(1)dwThreadId为目标线程id,当消息被投递出去后,MSG结构体中的hwnd会被自动设为NULL。

(2)目标线程的主消息循环中,当GetMessage(或PeekMessage)获取一条消息后,先检查hwnd是否为NULL,如果是就对这个消息进行一行特殊处理而不发往某个窗口过程,所以就不调用DispatchMessage将消息分派出去。

(3)与PostMessage一样,PostThreadMessage也是立即返回。调用线程无法知道消息是否被处理。

(4)获取创建窗口的线程ID:DWORD GetWindowThreadProcessID(hwnd,pdwProcessId)的返回值即为线程ID。

27.2.3 void PostQuitMessage(int nExitCode)

(1)终止消息循环。与PostThreadMessage(GetCurrentThreadId(),WM_QUIT,nExitCode,0)类似(但不相同)。PostQuitMessage并不真正投递消息到队列中去(即并不发送WM_QUIT消息,原因见27.4.3《从线程的队列中提取消息的算法》),而只是设置QS_QUIT标志位和nExitCode退出码。

(2)因为该函数只是设置QS_QUIT标志位和nExitCode,调用总是会成功,所以返回值为void。

27.3 向窗口发送消息

27.3.1 LRESULT SendMessage(hwnd,uMsg,wParam,lParam)

(1)只有消息被处理后,SendMessage才会返回。

(2)如果调用SendMessage的线程向该线程所建立的一个窗口发送消息,则SendMessage工作过程很简单,只是直接调用了指定的窗口过程,将其作为一个子函数调用而己。当窗口过程处理完消息后,其返回值直接返回给SendMessage,SendMessage再将其返回给调用线程。

(3)如果调用SendMessage向其他线程(包含其他进程中的线程)时,其过程较复杂,因为Windows要求窗口过程须由创建该窗口的线程来调用(而不能是其他线程),其工作过程如下:

  ①当调用SendMessage时,函数会将这条消息添加到接收线程的“Send-Message”队列中,这将导致开启接收线程的QS_SENDMESSAGE标志位

  ②如果此时接收线程正在执行“其他代码”,那么这条消息不会被立即处理。当接收线程开始等待消息时,系统会先检查QS_SENDMESSAGE标志位是否被设置。如果被设置,系统会从“Send-Message”队列中找到第一条消息(队列中可能有很多条消息,因为可能有多个线程在同一时间调用SendMessage向这个队列添加消息)。(注意,前面说的“其他代码”不包含调用GetMessage、PeekMeesage或WaitMessage等函数,因为这些函数会让接收线程取出Send-Message队列中的消息并开始处理)

  ③当接收线程从“Send-Message”队列中取出消息并调用相应的窗口过程后,处理一条消息后,GetMessage并不返回,而是再转去处理该队列的消息,直到“send-queue”队列中没有其他消息了,系统就会关闭QS_SENDMESSAGE标志位。在接收线程处理消息期间,调用SendMessage的线程(也被称为“调用线程”或“发送线程”)被设置为“空闲”以便等待接收线程发送一条应答消息到自己的“Reply-Message”队列。当接收线程处理完消息后,会把窗口过程的返回值投递到发送线程的“Reply-Message”队列(注意,这个返回值会作为SendMessage的返回值,返回给发送线程),并唤醒发送线程。然后发送线程继续正常执行。

  ④当发送线程等待SendMessage返回期间,他通常被设成“空闲”,但他也被允许执行一项任务:假如另一个线程(B)发送一个消息给当前这个线程调用SendMessage的线程(A),A线程必须立即去处理这个消息,而不必调用GetMessage、PeekMessage或WaitMessage等函数。为什么要立即处理?比如A线程SendMessage给B,这时A挂起等待B线程处理完毕,在这期间B也SendMessage给了A,如果A不被立即唤醒去处理B发送过来的消息,两个进程就会进入互相等待的死锁状态)

(4)Windows采用这种方法来处理线程间的SendMessage会导致发送线程挂起,如果这时接收线程出现一个Bug而导致死循环,这时发送线程也就无法被唤醒。这就意味着一个进程的Bug,可能影响到另一个进程。(可以调用以下四个函数来解决这个问题)

27.3.2 SendMessageTimeOut函数


参数


含义


HWND hwnd


前四个参数与SendMessage含义一样


UINT uMsg


WPARAM wParam


LPARAM lParam


UINT fuFlags


标志:可以是以下几个的组合

①SMTO_NORMAL(0):不使用任何其他标志,或取消以下几个标志或其组合。

②SMTO_ABORTIFHUNG:如果接收线程被挂起时,立即返回

③SMTO_BLOCK:一个线程在等待SendMessage*返回时可以被中断,以便处理另一个发送来的消息。使用SMTO_BLOCK标志阻止系统允许这种中断,但可能会造成死锁(原因见SendMessage部分的分析)。

④SMTO_NOTIMEOUTIFNOTHUNG:如果接收线程没有被挂起时,不考虑时间限定值。


UINT uTimeOut


等待其他线程应答我们发送的消息的时间最大值,单位ms


PDWORD pdwResult


处理消息的结果。


返回值


成功或失败,可用GetLastError获取更多的信息


备注:如果调用SendMessageTimeOut向调用线程所建立的窗口发送一个消息,系统只是调用这个窗口的过程,并将返回值赋给pdwResult。因为所有的处理都必须发生在一个线程里,调用该函数之后出现的代码必须等消息被处理完之后才会被执行。

27.3.3 SendMessageCallBack(hwnd,uMsg,wParam,lParam,pfnResultCallBack,dwData);

(1)调用SendMessageCallBack时,一条消息会被添加到接收线程的“Send-Message”队列,然后调用线程立即返回。当接收线程处理完消息后,会向发送线程的“Reply-Message”队列发送一条应答消息。然后系统合适的时候通知发送线程去执行pfnResultCallBack指定的回调函数

(2)回调函数的原型:VOID CALLBACK ResultCallBack(hwnd,uMsg,dwData,dwResult); 当调用SendMessageCallBack时,会其将自己的dwData参数直接传给回调函数,dwResult处理消息的窗口过程返回的结果。

(3)在线程间发送消息时,SendMessageCallBack会立即返回,但回调函数并不立即执行。即使接收线程处理完消息后,回调函数也不一定立即执行,因为接收线程只是发送个应答给发送线程,告知他处理完了消息。至于发送线程什么时候调用这个回调函数,由发送线程说了算,一般是当发送线程调用GetMessage、PeekMessage、WaitMessage时,消息从“Reply-Message”队列被取出时,回调函数才会被执行。

(4)SendMessageCallBack还有另一种用法。Windows提供了一种广播消息的方法,可以用系统中所有的重叠(Overlapped)窗口广播消息。虽然可以通过SendMessage,并向hwnd传递HWND_BROADCAST(-1),但这种方法的广播,其返回值只是一个LRESULT值,并不能查看每个重叠窗口的窗口过程处理后的返回结果。但如果调用SendMessageCallBack,对每个窗口过程处理会后,回调函数都会被调用一次,因此就可以通过dwResult查看返回结果。

(5)如果SendMessageCallBack向一个由调用线程所建立的窗口发送一个消息时,系统会立即调用窗口过程,并且在消息被处理之后,系统再调用回调函数。当回调函数返回之后,系统从调用SendMessageCallBack之后的代码开始执行。

27.7.4 BOOL SendNotifyMessage(hwnd,uMsg,wParam,lParam);

(1)SendNotifyMessage将一条消息添加到接收线程的“Send-Message”队列中并立即返回,这有点像PostMessage,但他与PostMessage有两点不同:

  ①如果向其他线程的窗口发送消息时, SendNotifyMessage是向接收线程的“Send-Message”添加消息的,而PostMessage是向接收线程的“Posted-Message”队列添加消息的。在系统的消息机制中, “Send-Message”队列中的消息总是比“Posted-Message”队列中的消息被优先处理

  ②其次,当向调用线程自己创建的窗口发送消息时,SendNotifyMessage的工作就像SendMessage一样,直接调用窗口过程,同时等待窗口过程处理完才返回。

(2)很多消息只是用于通知的目的,用于告知应用程序某个状态发生了改变,所以发送线程没有必要等待接收线程处理完毕才返回。(如系统向窗口发送WM_SIZE、WM_MOVE等,操作系统没有必要停下来等用户处理了这些消息再继续。但有些消息,系统必须等待,如操作系统向一个窗口发送WM_CREATE,必须等待,如果窗口过程返回-1,则系统不建立这个窗口。

27.3.5 BOOL ReplyMessage(LRESULT lResult)

(1)接收线程可以在窗口过程还没处理完消息的情况下,提前向通过SendMessage发送消息的线程的“Reply-Message”添加一个应答消息,这会唤醒发送线程

(2)接收线程把lResult作为消息处理结果传递给发送线程。当调用ReplyMessage后,发送线程被提前唤醒。当接收线程真正从窗口过程中返回时,系统将忽略这个返回值,即不再向发送线程回复本应在窗口过程正常结束才发送的应答消息

(3)ReplyMessage必须在接收消息的窗口过程中被调用,而不能由某个调用Send*的线程调用,因为他是用来回复调用SendMessage的线程。前面讨论过的3个SendMessage*函数会立即返回。SendMessage函数的返回,可以由窗口过程的实现者通过调用ReplyMessage来控制。

(4)如果消息不是通过SendMessage发送的,或者消息由同一个线程发送,ReplyMessage不起作用。这也是返回值指出的,如果在处理线程间的消息发送时调用了ReplyMessage返回TRUE。处理线程内的消息发送时调用ReplyMessage会返回FALSE。

(5)可以调用InSendMessage(Ex)来确定是线程间的消息发送还是线程内的发送消息。如果是线程间发送的会返回TRUE,线程内Send或Post的会返回FALSE。

【Sending a Message示例】

//WM_USER+5是由其他线程发送过来的消息。如果是其他进程发送过来的消息,那个进程里要先调用RegisterWindowMessage注册为全局的消息类型
case WM_USER + 5:
    ……//处理些其他事情
    if (InSendMessage())    //是否是线程间发送的消息,只有线程间的才能Reply
        ReplyMessage(TRUE); //该消息己经处理了差不多了,SendMessage的线程可以被唤醒了,因为后面我还要做些其他事情,比如//弹出对话框(DialogBox),你就不要傻等下去了。注意:在哪里调用ReplyMessage就可以在那个什么//通知系统去唤醒SendMessage线程

    DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc);

break;
时间: 2024-10-25 12:58:08

第27章 窗口与消息(1)的相关文章

《Windows程序设计 第5版》第3章 窗口与消息 笔记

广告先: LibUIDK界面库:制作QQ.360界面时,你能找到的最强大的界面库.基于DirectHWND技术! 3.1 窗口的创建 一个简单的win32程序如下(假设工程名为"HelloWin32",下面的代码是使用vc6.0创建一个名为HelloWin32的"Win32 Application",并且选择"A typical "Hello World" application"后创建的代码精简后得到): // HelloW

第3章 窗口与消息_3.2面向对象的窗口类的封装

3.2.1 MFC单文档大致框架 (1)MFC类继承略图 (2) MFC单文档应用程序类层次结构图 CWinApp:应用程序类,每个应用程序有且只有一个继承于CWinApp的派生类对象 CWnd:是一个通用的窗口类,用于提供Windows中的所有通用特性.对话框和控件. (3)应用程序执行过程图 3.2.2 应用程序QWinApp的设计 //QWinApp.h文件 #include "stdAfx.h" class QWinApp { public: QWinApp(); ~QWinA

《Windows程序设计》读书笔三 窗口与消息

第三章 窗口于消息 前面的例子都使用MessageBox来创建窗口 ,单他所创建的窗口灵活性有限. 3.1 窗口的创建 只要调用CreateWindow函数即可 3.1.1 系统结构概述 一个应用程序窗口可能包含,标题栏,菜单栏,工具栏,滚动条.另外还有一种类型的窗口是对话框,这种窗口可以不带标题栏 还可能包含,按钮,单选按钮,复选框,列表框,滚动条,文本框等.每一个这些对象都被称为 子窗口,或者 控件窗口 当用户改变窗口尺寸时,Windows便向应用程序发送一条携带新窗口尺寸相关的信息,接着应

第三章、窗口与消息

应用程序的每个窗口都有一个窗口过程函数与之关联,Windows通过调用窗口过程向窗口传递消息.窗口过程依据消息做相应处理,然后将控制权返还给Windows. 窗口类标识了用于处理传递给窗口的消息的窗口过程,允许多个窗口共享同一窗口类. 消息队列中存放着应用程序可能创建的所有窗口的消息,消息循环从消息队列中检索消息,并将其分发给相应的窗口过程. #include <Windows.h> #include <mmsystem.h> #pragma comment(lib, "

微软私有云分享(R2)27维护窗口的使用

维护窗口提供了计划外维护System Center 2012 R2的可能.在维护窗口所约定的时间内,可以独自对Hyper-V主机进行一些基础维护,如打补丁,关机更换硬件等操作.对于处于维护窗口的Hyper-V主机,SCVMM2012 R2不对其进行监视.同时相应的操作也不会在处于维护窗口中的Hyper-V主机进行.包括本章介绍的动态优化和电源优化,也需要在维护窗之外运行. 第1步,在"设置"对话框,点击"维护窗口"后,于顶部工具栏点击"创建维护窗口&quo

第27章 CSS传统布局(下)

第 27章 CSS传统布局[下]学习要点:1.定位布局2.box-sizing3.resize 本章主要探讨 HTML5中 CSS早期所使用的传统布局,很多情况下,这些布局方式还是非常有用的.一.定位布局在使用定位布局前,我们先了解一下定位属性的用法.CSS2提供了position属性来实现元素的绝对定位和相对定位. 属性 说明static 默认值,无定位.absolute 绝对定位,使用 top.right.bottom.left进行位移.relative 相对定位,使用 top.right.

窗口和消息

窗口和消息 壹佰软件开发小组  整理编译   在前两章,程序使用了同一个函数MessageBox来向使用者输出文字.MessageBox函数会建立一个「窗口」.在Windows中,「窗口」一词有确切的含义.一个窗口就是屏幕上的一个矩形区域,它接收使用者的输入并以文字或图形的格式显示输出内容. MessageBox函数建立一个窗口,但这只是一个功能有限的特殊窗口.消息窗口有一个带关闭按钮的标题列.一个选项图标.一行或多行文字,以及最多四个按钮.当然,必须选择Windows提供给您的图标与按钮. M

Lua_第27章 User-Defined Types in C

Lua_第27章  User-Defined Types in C 在上一章,我们讨论了如何使用 C 函数扩展 Lua 的功能,现在我们讨论如何使用 C 中新创建的类型来扩展 Lua.我们从一个小例子开始,本章后续部分将以这个小例子 为基础逐步加入 metamethods 等其他内容来介绍如何使用 C 中新类型扩展 Lua. 我们的例子涉及的类型非常简单,数字数组.这个例子的目的在于将目光集中到 API 问题上,所以不涉及复杂的算法.尽管例子中的类型很简单,但很多应用中都会用到这 种类型.一般情

眼见为实(2):介绍Windows的窗口、消息、子类化和超类化

眼见为实(2):介绍Windows的窗口.消息.子类化和超类化 这篇文章本来只是想介绍一下子类化和超类化这两个比较"生僻"的名词.为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程.子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法.不要把"子类化.超类化"与面向对象语言中的派生类.基类混淆起来."子类化.超类化"中的"类"是指W