Windows API笔记(九)窗口消息

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


文章目录

  • 1. 线程知识点
  • 2. 线程队列和消息处理
    • 2.1 Win32消息队列结构
    • 2.2 向线程的消息队列投递消息
    • 2.3 发生消息给窗口
    • 2.4 唤醒线程


1. 线程知识点

  1. 进程是指一个运行程序的实例,每个进程至少有一个线程,进程的生命周期同它拥有的线程相关联
  2. 线程是指进程种的一条执行路径
  3. 操作系统在线程间调度CPU时间
  4. 多线程是为了抢占更多的CPU时间
  5. 当线程执行时,系统可以从该线程中“偷”走CPU,而把CPU给另一个线程。但当CPU正在执行一条指令时(CPU指令,不是源代码行),它不能被中断。操作系统的这种几乎能在任何时候中断一个线程而把CPU分配给另一个等待的线程的能力叫做抢先式多任务
  6. 线程创建的大部分对象都归进程所有。只有两种用户对象–窗口和钩子函数归线程所有

2. 线程队列和消息处理

Win32应用程序所做的大部分工作都是由窗口消息触发的。

发生窗口消息的过程:

  1. 应用程序向另一个任务创建的窗口发送一条消息
  2. 调用线程通知接收线程它需要执行某个行动
  3. 调用线程自我挂起,直到接收线程完成了请求

2.1 Win32消息队列结构

Win32每个线程都运行在一个独立的线程环境中,具体来说,每个线程必须有完全不受其他线程影响的消息队列。而且,每个线程必须有一个模拟的环境允许它维护自己对键盘焦点、窗口活动、鼠标捕获等事件。

当线程被创建第一个用户或GDI函数时,系统还创建了一个THREADINFO结构,把它关联给线程,线程成为了用户界面线程。该结构包含有一组成员变量,使得线程认为它运行在自己的环境中。该结构标识了线程的投递的消息队列、发送的消息队列、回答消息队列、虚拟输入队列和唤醒标志,还要一些用于线程局部输入状态的变量。

2.2 向线程的消息队列投递消息

在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在投递消息后立即返回。调用得线程不知道消息是否被处理。

2.3 发生消息给窗口

可以调用SendMessage函数来直接向一个窗口过程发生消息:

LRESULT
SendMessage(
    _In_ HWND hWnd,
    _In_ UINT Msg,
    _Pre_maybenull_ _Post_valid_ WPARAM wParam,
    _Pre_maybenull_ _Post_valid_ LPARAM lParam);

窗口过程将处理该消息,而且在处理完消息之后,才返回SendMessage给调用者

由于是同步发送,有可能会一直等待,有4个函数保护程序不至于陷入这种状况:

  1. 指定响应超时时间:
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);
  1. 已回调方式获取处理结果(异步):
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);
  1. 类似PostMessage
BOOL
SendNotifyMessage(
    _In_ HWND hWnd,
    _In_ UINT Msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam);
  • 发送至另一个线程创建的窗口时,在发送消息队列中放置一条消息后立即返回,但发送的消息比PostMessage发送到投递消息队列有更高的优先级。
  • 发送至同一线程创建的窗口时,SendNotifyMessage与SendMessage完全相同,也是在消息处理完才返回。
  1. ReplayMessage

2.4 唤醒线程

当线程调用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的参数中。

你可能感兴趣的:(C/C++)