Windows API笔记(一)内核对象
Windows API笔记(二)进程和进程间通信、进程边界
Windows API笔记(三)线程和线程同步、线程局部存储
Windows API笔记(四)win32内存结构
Windows API笔记(五)虚拟内存
Windows API笔记(六)内存映射文件
Windows API笔记(七)堆
Windows API笔记(八)文件系统
Windows API笔记(九)窗口消息
Windows API笔记(十)动态链接库
Windows API笔记(十一)设备I/O
Win32应用程序所做的大部分工作都是由窗口消息触发的。
发生窗口消息的过程:
Win32每个线程都运行在一个独立的线程环境中,具体来说,每个线程必须有完全不受其他线程影响的消息队列。而且,每个线程必须有一个模拟的环境允许它维护自己对键盘焦点、窗口活动、鼠标捕获等事件。
当线程被创建第一个用户或GDI函数时,系统还创建了一个THREADINFO结构,把它关联给线程,线程成为了用户界面线程。该结构包含有一组成员变量,使得线程认为它运行在自己的环境中。该结构标识了线程的投递的消息队列、发送的消息队列、回答消息队列、虚拟输入队列和唤醒标志,还要一些用于线程局部输入状态的变量。
在Win32中,当线程开始被创建时,它被认为是一个工作者线程,即系统视图不偏不斜的对待它。但线程一旦开始调用用户或GDI函数时,它就成为用户界面线程。用户界面线程需要额外的开销,如THREADINFO结构等。换句话说,线程在调用第一个用户或GDI函数之前,没有相关的THREADINFO结构。
一旦线程有了相关的THREADINFO结构,它就有了自己的消息队列集。如果一个进程创建了10个线程,所有的线程都调用了CreateWindow(用户函数),就会有10个消息队列集。通过调用PostMessage函数将消息放在一个进程的投递消息队列:
BOOL
PostMessage(
_In_opt_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
当线程调用此函数时,系统判断哪个线程创建了由hWnd参数标识的窗口,再将指定的消息传递给相应的线程的投递消息队列。PostMessage在投递消息后立即返回,调用的线程不知道投递的消息是否被指定的窗口的线程处理了。实际上,指定的窗口可能会接收不到投递的消息。如果创建指定窗口的线程在处理其队列中所有消息之前终结了,就会发生这种情况。
也可以调用PostThreadMessage来将消息放入线程的投递消息队列中:
BOOL
PostThreadMessage(
_In_ DWORD idThread,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
WINUSERAPI
指定的线程是由参数idThread标识的。当消息被放入队列中,MSG结构中的hWnd成员被设置为NULL。该函数通常被一个应用程序在其主消息循环中执行某些特别处理时调用。该线程的主消息循环被写成这样,使得在GetMessage或PeekMessage取得一个消息后,代码检查hWnd是否为NULL,并且检查MSG结构得msg成员来进行特别得处理。如果线程判断该消息不属于一个窗口,就不调用DispatchMessage,消息循环接着取下一条消息。
像PostMessage一样,PostThreadMessage在投递消息后立即返回。调用得线程不知道消息是否被处理。
可以调用SendMessage函数来直接向一个窗口过程发生消息:
LRESULT
SendMessage(
_In_ HWND hWnd,
_In_ UINT Msg,
_Pre_maybenull_ _Post_valid_ WPARAM wParam,
_Pre_maybenull_ _Post_valid_ LPARAM lParam);
窗口过程将处理该消息,而且在处理完消息之后,才返回SendMessage给调用者。
由于是同步发送,有可能会一直等待,有4个函数保护程序不至于陷入这种状况:
LRESULT
SendMessageTimeout(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam,
_In_ UINT fuFlags,
_In_ UINT uTimeout,
_Out_opt_ PDWORD_PTR lpdwResult);
BOOL
SendMessageCallback(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam,
_In_ SENDASYNCPROC lpResultCallBack,
_In_ ULONG_PTR dwData);
typedef VOID (CALLBACK* SENDASYNCPROC)(HWND, UINT, ULONG_PTR, LRESULT);
BOOL
SendNotifyMessage(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
当线程调用GetMessage或WaitMessage而没有窗口消息时,系统就把它挂起,不再分配它得CPU时间。一旦有消息投递或发送给线程时,系统就设置唤醒标志来指出该线程现在该被分配CPU时间来处理消息。在一般情况下,用户不进行键盘输入或移动鼠标,没有消息发送给任意窗口。这意味着系统中得大部分线程没有被分配CPU时间。
当线程运行时,它可以调用GetQueueStatus函数来查询它得队列状态:
DWORD
GetQueueStatus(
_In_ UINT flags);
参数flags是一个标志或一些标志“或”在一起,它允许测试指定得唤醒位。下表显示了可能得标志值和它们的含义:
标志 | 队列中的消息 |
---|---|
QS_KEY | WM_KEYUP,WM_KEYDOWN,WM_SYSKEYUP,WM_SYSKEYDOWN |
QS_MOUSE | 与QS_MOUSEMOVE|QS_MOUSEBUTTON相同 |
QS_MOUSEMOVE | WM_MOUSEMOVE |
QS_MOUSEBUTTON | WM_?BUTTON* (? : L、M或R,* 为 DOWN、UP或DBLOCK) |
QS_PAINT | WM_PAINT |
QS_POSTMESSAGE | 投递的消息(不是来自硬件输入事件) |
QS_SENDMESSAGE | 另一线程发送的消息(也包含了回答消息) |
QS_TIMER | WM_TIMER |
QS_HOTKEY | WM_HOTKEY |
QS_INPUT | 与QS_MOUSE|QS_KEY相同 |
QS_ALLEVENTS | 与QS_INPUT|QS_POSTMESSAGE|QS_TIMER|QS_PAINT|QS_HOTKEY相同(这里不包括QS_SENDMESSAGE,因为它是保留作系统内部使用的) |
QS_ALLINPUT | 与QS_ALLEVENTS|QS_SENDMESSAGE相同 |
当调用GetQueueStatue函数时,参数flags告诉GetQueueStatus要检测的队列中的消息的类型。而后,函数返回时,当前在队列中的消息的类型在返回值的高字节中,返回的标志集总是要求检测的标志集的子集。
例如:
BOOL bPaintMsgWaiting = HIWORD(GetQueueStatus(QS_TIMER)) & QS_PAINT;
不管队列中是否有WM_PAINT消息,bPaintMsgWaiting的值总是FALSE,因为QS_PAINT没有作为一个标志指定在传递给GetQueueStatus的参数中。