所有基于事件驱动的操作系统中的GUI程序,都会在主线程中运行一个消息泵来从消息队列中取出消息并执行对应的处理逻辑。消息队列中的消息除了由系统产生外,还提供了对应的API接口来将消息存放到消息队列中去。在Windows中所有线程中都可以有消息队列,并且可以建立消息泵来从消息队列中取消息,通过消息队列来进行数据的传递也是一种线程同步的机制。每个线程在建立时都会有一个THREADINFO结构,这个结构是一个未公开的内部数据结构。这个结构的定义大概如下:
struct THREADINFO
{
登记消息队列指针(posted-message queue)
发送消息队列指针(sended-message queue)
应答消息队列指针(reply-message queue)
虚拟输入队列指针(virtualized-input queue)
唤醒标志(wake flag)
结束代码(nExitCode)
// 下面是用来存放GUI线程的信息
线程的活动窗口句柄
线程的焦点窗口句柄
线程鼠标捕获窗口句柄
线程的拥有提示符的窗口句柄
其他线程的状态变量
};
从上面看出每个线程有四个队列:一个登记消息队列,一个发送消息队列,一个应答消息队列,一个虚拟输入消息队列。每个线程在建立初始时并不会为线程建立消息队列,直到线程调用了与图形用户界面有关的函数(例如:CreateWindow, GetMessage)就会为线程建立消息队列。操作系统维护着一个系统消息队列和分别为每个GUI线程维护消息队列。当系统收到用户键盘和鼠标的输入时,键盘鼠标的驱动程序就会产生一个消息,并将消息投递到系统消息队列中,系统每一次从系统消息队列中检查一个消息,确定接收消息的目标线程,然后将消息从系统消息队列中删除,并把消息投递到线程的登记消息队列中。系统在运行的时候一个时间内只能有一个线程的窗口具有活动窗口,键盘输入焦点,鼠标捕获,和提示符。虽然每个TRHEADINFO都维护线程的这些信息,但当线程不具备活动窗口,键盘焦点,鼠标捕获和提示符时这些成员变量的值都将为NULL。下面将列出所有对消息队列操作的API。
BOOL PostMessage(
HWND hWnd, //NULL为向当前线程投递消息,为HWND_BROADCAST则向系统的所有Top-Level窗口发送消息
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
); //函数立即返回,失败返回0。 一般不要用这个函数来投递WM_QUIT消息
BOOL PostThreadMessage(
DWORD idThread, // thread identifier
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
对于发送给线程的消息,由于没有窗口回调过程(WndCallback)会处理,因此需要在消息循环中处理发送给线程的消息。
VOID PostQuitMessage(
int nExitCode // exit code
); //这个函数通常是在程序主窗口的WM_DESTROY消息处理中调用
这个函数的内部实现类似(注意只是类似):
PostThreadMessage(GetCurrentThreadId(), WM_QUIT, nExitCode, 0);
但是PostQuitMessage函数并没有将WM_QUIT消息放到线程的登记消息队列中去,而只是将线程的THREADINFO结构中的唤醒标志设置为QS_QUIT。并将THREADINFO中的退出代码设置为nExitCode, 这样当线程调用GetMessage函数时若检测到唤醒标志为QS_QUIT则将THREADINFO结构的退出代码赋值给MSG的wParam成员,并且返回FALSE,这样就可以使消息循环结束,并调用: return msg.wParam;
LRESULT SendMessage(
HWND hWnd, // handle to destination window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
这个函数要直到相应的消息处理完成后才返回。这个函数对于处理发送给本线程的消息和发送给别的线程的消息的处理不同。对于发送给本线程的消息(hWnd是本线程的窗口句柄),则SendMessage函数直接调用相应的窗口的回调函数,让后将回调函数的返回作为SendMessage函数的返回;而对于将消息发送给别的线程的窗口(hWnd并不是本线程的窗口)时则处理比较复杂:
一. 发送线程将消息追加到接收线程的发送消息队列中,并将接收线程的唤醒标志设置为QS_SENDMESSAGE,然后发送线程进入阻塞状态。
二. 接收线程等到调用GetMessage(或PeekMessage, WaitMessage)时先检查本线程的唤醒标志是否为QS_SENDMESSAGE,若是则将本线程中的发送消息队列中的消息取出,一直处理完发送消息队列中的所有消息,当每完成一条消息的处理后,接收线程就会将处理的结果追加到发送线程的应答消息队列中。
三. 当发送线程检测到本线程的应答消息队列中有消息时就将本线程激活并将应答消息队列中的结果做为发送线程的SendMessage函数的返回值。
四. 在发送线程等待结果前进入阻塞的这段时间里,若发现本线程的发送消息对列中有消息时,也会去处理本线程的所有发送消息队列中的消息,也就是说,在SendMessage函数返回前,可以确保本线程的发送消息队列中没有任何消息,并且应答消息队列中也没有任何消息。
发送消息的函数SendMessage可能造成线程之间进入死锁状态,因此可以使用下面一系列函数来防止发送消息时产生死锁(用于不同线程之间发送):
(1). 具有超时机制的发送消息函数
BOOL SendMessageTimeout(
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam, // second message parameter
UINT fuFlags, // send options
UINT uTimeout, // time-out duration
PDWORD_PTR lpdwResult //[OUT] return value for synchronous call
); //函数调用失败或超时返回0,正确返回TRUE.
fuFlags: 可以是如下联合: SMTO_NORMAL: 正常为0, 不与其他联合 SMTO_ABORTIFHUNG: 若是接收线程正处于挂起状态时则只将消息放入接收线程的发送消息队列中,并且函数立即返回 SMTO_BLOCK: 在等待应答时,不处理本线程发送消息队列中的消息 SMTO_NOTIMEOUTIFNOTHUNG: 若接收线程没有挂起时,则忽略uTimeOut参数
uTimeout: 发送线程等待的时间,为豪秒 lpdwResult: 保存消息返回的结果
(2).具有回调函数的发送消息函数
BOOL SendMessageCallback(
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam, // second message parameter
SENDASYNCPROC lpCallBack, // callback function
DWORD dwData // 传递给回调函数的参数
); //函数不是返回消息的结果
这个函数会立即返回,而当接收消息的线程处理完后会将一个应答消息放入发送消息的应答消息队列中,直到发送消息线程有函数来取应答消息队列中的消息时,回调函数才能调用,当发送广播消息时,每个顶级窗口处理完后都会使的发送线程执行一次回调函数。回调函数的格式如下:
VOID CALLBACK SendAsyncProc(
HWND hwnd, // handle to destination window
UINT uMsg, // message
ULONG_PTR dwData, // application-defined value
LRESULT lResult // result of message processing, 消息处理产生的结果
); //对于本线程发送消息来说,当调用完窗口过程后,立即调用这个回调函数,回调函数执行完后,继续执行SendMessage后的代码
BOOL SendNotifyMessage(
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
这个函数对于发送给本线程时跟SendMessage相同,而发送给不同线程时,则将消息追加到接收线程的发送消息队列,然后立即返回。这个函数的返回值不是消息的返回值而是判断函数调用正确与否的返回值。这个函数不会使发送线程进入阻塞,也不会在本线程中加入应答消息.
BOOL ReplyMessage(
LRESULT lResult // message-specific reply
);
当一个线程调用ReplyMessage时,它是要告诉系统,为了知道消息结果,它已经完成了足够的工作,结果应该包装起来并登记到发送线程的应答消息队列中。这将使发送线程醒来,获得结果,并继续执行。调用ReplyMessage的线程在lResult参数中指出消息处理的结果。在调用ReplyMessage之后,发送消息的线程恢复执行,而处理消息的线程继续处理消息。两个线程都不会被挂起,都可以正常执行。当处理消息的线程从它的窗口过程返回时,它返回的任何值都被忽略。这里的问题是, ReplyMessage必须在接收消息的窗口过程中调用,而不是由调用某个SendXXX函数的线程调用。为了编写保护性代码,最好不要用前面讨论过的三个SendXXX函数中的一个代替对SendMessage的调用,而是依靠窗口过程的实现者来调用ReplyMessage。还应该知道,如果在处理一个由同一线程发送来的消息时调用ReplyMessage,则该函数什么也不做。实际上,这就是ReplyMessage的返回值所指出的。如果你在处理线程间的消息发送时调用了ReplyMessage,则它返回TRUE ,如果你在处理线程内的消息发送时调用ReplyMessage,它返回FALSE
BOOL InSendMessage(VOID); //是其他线程发来的消息返回TRUE,本线程发来或登记的消息返回FALSE
DWORD InSendMessageEx(
LPVOID lpReserved // not used; must be NULL
);
扩展函数的返回值若为ISMEX_NOSEND(0)则表示是线程内发来或登记的消息,否则为如下联合:
ISMEX_CALLBACK: 表明发送线程是调用SendMessageCallback函数发送的,发送线程没有处于阻塞状态. ISMEX_NOTIFY : 表示发送线程是调用的SendNotifyMessage函数发送的 ISMEX_REPLIED: 表明接收线程已经调用了ReplyMessage函数,而发送线程也没有处于阻塞状态了 ISMEX_SEND :发送线程使用了SendMessage或SendMessageTimeout函数发来的消息,并且发送线程正处于阻塞状态,等待着应答
UINT RegisterWindowMessage(
LPCTSTR lpString // message string
);
函数调用成功返回0XC000到0XFFFF之间的值,失败返回0, 对于不同进程之间使用相同的消息名称所得到的消息ID的值是一样的.消息的分配如下:
0x0000-0x03FF: 为系统定义的消息 0x0400-0x7FFF: 为每个窗口类私有的消息 #define WM_USER 0x0400 0x8000-0xBFFF: 为应用程序私有的消息 #define WM_APP 0x8000 0xC000-0xFFFF: 为调用RegisterWindowMessage函数产生的系统唯一的消息 0xFFFF- : 保留给系统未来使用
一. 获取最近的GetMessage函数所检索到的消息时的cursor的位置
DWORD GetMessagePos(VOID); //低字X坐标, 高字Y坐标
二. 获取最近的GetMessage函数所检索的消息放置在队列中的时间
LONG GetMessageTime(VOID); //单位豪秒,这个时间若超过了某个时间又会归0
LPARAM SetMessageExtraInfo(
LPARAM lParam // application-defined value ,将一个程序定义的附加信息的值与调用函数的线程的消息队列关联起来
); //函数返回前一个关联的信息
LPARAM GetMessageExtraInfo(VOID); //获取
BOOL GetInputState(VOID); 判断当前消息队列中是否有键盘和鼠标的消息,有返回TRUE,没有返回FALSE
DWORD GetQueueStatus(
UINT flags // message types
); //线程的消息队列的状态存放在THREADINFO结构的唤醒标志成员中,用户可以联合指定属性,函数的高字返回指定属性的一个子集,低字返回自上一次调用GetMessage/PeekMessage/GetQueueStatus函数以来存放在消息队列中的消息的类型的联合
BOOL GetMessage(
LPMSG lpMsg, //[OUT] message information
HWND hWnd, // handle to window,指定只获取属于窗口和窗口子窗口的消息,若为NULL则所有的窗口的消息和线程消息
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax // last message //若两个都为0则表示不限制
); //若消息队列中没有投递消息则调用线程将会被挂起,若函数检测到QS_QUIT标志则返回FALSE, 否则返回TRUE,函数调用失败返回-1
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time; //消息存放在队列中的时间
POINT pt; //消息产生时鼠标的位置
} MSG, *PMSG;
BOOL PeekMessage(
LPMSG lpMsg, // message information
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax, // last message
UINT wRemoveMsg // removal options
); //若是消息队列中没有投递消息则函数立即返回
PeekMessage和GetMessage都会在内部处理完线程的所有发送消息队列中的消息
BOOL WaitMessage(VOID);
当调用这个函数后线程将会挂起,直到线程的消息队列中有消息时线程才会恢复执行