Windows 消息队列

在 Windows 操作系统的庞大体系中,消息队列扮演着至关重要的角色,它是应用程序与操作系统之间,以及应用程序内部不同组件之间进行通信的关键机制。理解 Windows 消息队列,对于开发者来说,就像是掌握了一把打开 Windows 编程世界更深层次大门的钥匙。

消息队列原理

消息队列的基本概念

在 Windows 系统中,每个创建了窗口的线程都拥有一个属于自己的消息队列,专门用于存储等待处理的各种消息。这些消息来源广泛,涵盖了用户动作,比如鼠标点击、键盘操作,以及系统通知,像窗口重绘请求、系统关闭通知等。例如,当用户在窗口中点击鼠标时,系统会立即生成一个鼠标点击的消息,并将其投递到该窗口所属线程的消息队列中。

消息的分类

  • 系统消息:主要涉及窗口生命周期的管理。比如WM_CLOSE消息,当用户点击窗口的关闭按钮时,系统就会向窗口发送这个消息,告知窗口即将被关闭;WM_QUIT消息则通常用于通知应用程序退出。
  • 硬件消息:反映的是用户与硬件设备的交互。例如WM_KEYDOWN消息,当用户按下键盘上的某个键时,就会产生这个消息;WM_MOUSEMOVE消息则在鼠标移动时被触发。
  • 自定义消息:开发者可以根据自己的需求定义特定的消息。自定义消息一般从WM_USER(其值为 0x0400)开始,到 0x7FFF 这个范围内。通过自定义消息,开发者能够实现一些特定的功能逻辑,满足应用程序的特殊需求。

消息循环

线程通过一个被称为消息循环的循环机制,从其消息队列中检索消息。消息循环的基本操作主要包含以下几个步骤:

  • 检索消息:可以使用GetMessage或PeekMessage函数来实现。GetMessage函数用于从调用线程的消息队列中检索消息,它是阻塞的,如果没有消息,就会一直等待消息的到来,直到有消息到达时才返回;如果遇到退出消息WM_QUIT,则返回FALSE。而PeekMessage函数用于非阻塞地检查调用线程的消息队列,它允许查看消息队列中的消息而不必移除它,还可以配置为从队列中移除消息或仅检查消息而不移除,这在动画或游戏编程中非常有用,能够确保应用程序在保持响应用户操作的同时,继续进行其他处理。
  • 翻译消息:使用TranslateMessage函数来转换键盘输入。该函数会将虚拟键消息转换为字符消息,比如将用户按下的键转换为对应的 ASCII 码字符。
  • 分发消息:通过DispatchMessage函数将消息派发给目标窗口的窗口过程。窗口过程是一个回调函数,由开发者定义,用于处理接收到的各种消息,根据不同的消息类型执行相应的操作。

消息的传递

消息传递过程解析:以鼠标点击事件为例

当用户在窗口上进行鼠标点击操作时,消息传递过程如下:

  1. 硬件中断:鼠标设备检测到点击动作后,向操作系统发送硬件中断信号。操作系统的内核收到这个中断信号,开始处理这个鼠标事件。
  1. 系统生成消息:操作系统内核根据鼠标点击的坐标、按键状态等信息,生成相应的鼠标消息,如WM_LBUTTONDOWN(鼠标左键按下)、WM_LBUTTONUP(鼠标左键抬起)等。这些消息被放入系统的消息队列中。
  1. 消息投递到线程消息队列:系统会根据鼠标点击位置所对应的窗口,找到该窗口所属线程的消息队列,并将生成的鼠标消息投递到这个线程消息队列中。
  1. 消息循环处理:拥有该窗口的线程通过消息循环不断从自己的消息队列中检索消息。GetMessage或PeekMessage函数会从消息队列中获取到鼠标消息。
  1. 消息翻译(可选):对于一些键盘消息,可能需要使用TranslateMessage函数将虚拟键消息转换为字符消息,但鼠标消息通常不需要这一步。
  1. 消息分发:通过DispatchMessage函数将鼠标消息派发给目标窗口的窗口过程。窗口过程根据接收到的鼠标消息类型,执行相应的操作,比如更新窗口显示内容、触发按钮点击事件对应的逻辑等。

消息传递的函数接口

在 Windows 消息处理中,有四个核心的接口函数,它们各自承担着不同的角色和功能,共同协作完成消息的传递。

BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

  • lpMsg:是一个指向MSG结构的指针,该结构将接收消息的详细信息,包括消息的类型、发送者、附加参数等。
  • hWnd:指定窗口的句柄,如果为NULL,则接收属于调用线程的任何窗口的消息。
  • wMsgFilterMin和wMsgFilterMax:指定要检索的消息范围的最小值和最大值。如果两者都为 0,函数将返回所有可用的消息。

功能和特点:从调用线程的消息队列中检索消息,遇到退出消息WM_QUIT返回FALSE,是阻塞函数。

BOOL PeekMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);

  • lpMsg:同GetMessage中的lpMsg。
  • hWnd:同GetMessage中的hWnd。
  • wMsgFilterMin和wMsgFilterMax:同GetMessage中的wMsgFilterMin和wMsgFilterMax。
  • wRemoveMsg:指定消息如何处理,常用值有PM_REMOVE(从队列中移除消息)、PM_NOREMOVE(仅检查消息而不移除)。

功能和特点:非阻塞地检查调用线程的消息队列,可配置是否移除消息,常用于动画或游戏编程。

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

  • hWnd:接收消息的窗口的句柄。
  • Msg:消息的标识符,用于标识消息的类型。
  • wParam和lParam:消息特定的附加信息,具体含义取决于消息的类型。

功能和特点:同步发送消息,调用方在接收窗口处理该消息之前会阻塞,可用于发送任何类型的消息,并且能够获取消息处理的结果。

BOOL PostMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

  • hWnd:接收消息的窗口的句柄。
  • Msg:消息的标识符。
  • wParam和lParam:消息特定的附加信息。

功能和特点:异步发送消息,将消息放入消息队列后立即返回,不等待消息被处理,适合用于那些不需要立即反馈的消息发送,如状态更新或通知消息。

进程间消息传递示例

以两个进程之间通过消息队列进行通信为例,假设进程 1 要发送消息给进程 2。首先,需要定义一个自定义消息,比如:

#define WM_CUSTOMMSG (WM_USER+100)

在进程 2 中,需要定义一个窗口过程来处理接收到的消息:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_CUSTOMMSG:

{

// 处理接收到的消息,这里假设消息携带了一个字符串

wchar_t* receivedMessage = reinterpret_cast(lParam);

// 例如,将接收到的字符串设置到某个窗口控件的文本中

SetWindowText(hLabel, receivedMessage);

}

break;

// 其他消息处理...

}

return 0;

}

在进程 1 中,通过PostMessage或SendMessage函数来发送自定义消息:

// 假设已经获取到进程2窗口的句柄hWnd2

wchar_t* messageToSend = L"Hello, Process 2!";

// 使用PostMessage异步发送消息

PostMessage(hWnd2, WM_CUSTOMMSG, 0, reinterpret_cast(messageToSend));

// 或者使用SendMessage同步发送消息

// SendMessage(hWnd2, WM_CUSTOMMSG, 0, reinterpret_cast(messageToSend));

然而,在进程间通信时需要注意数据传递的安全性。例如,如果直接传递一个指向进程 1 内存中数据的指针,进程 2 是无法直接访问该数据的,因为进程 1 准备发送的数据存储在进程 1 的虚拟内存中,进程 2 无法通过地址来获取这个数据。为了解决这个问题,Windows 提供了WM_COPYDATA消息,用于在 Windows 应用程序之间安全地传递数据。

WM_COPYDATA 消息原理及使用

当一个应用程序需要向另一个应用程序发送数据时,可以将数据封装在COPYDATASTRUCT结构中,并通过SendMessage函数发送WM_COPYDATA消息。

  • COPYDATASTRUCT 结构定义

typedef struct tagCOPYDATASTRUCT {

ULONG_PTR dwData; // 任意值,由发送方设置,接收方可以用它来识别数据

DWORD cbData; // lpData指向的数据的大小,以字节为单位

PVOID lpData; // 指向要传递的数据的指针

} COPYDATASTRUCT;

参数:

dwData:这是一个用户定义的数据值,发送方可以使用它来传递额外的信息或数据类型标识,接收方则可以用它来决定如何解释接收到的数据。

cbData:表示lpData指向的数据的大小(以字节为单位)。

lpData:是一个指针,指向实际要传输的数据。

  • 使用示例

在进程 1 中发送数据:

// 假设已经获取到进程2窗口的句柄hWnd2

COPYDATASTRUCT cds;

cds.dwData = 0; // 自定义数据标识

wchar_t dataToSend[] = L"Data from Process 1";

cds.cbData = (wcslen(dataToSend) + 1) * sizeof(wchar_t);

cds.lpData = dataToSend;

SendMessage(hWnd2, WM_COPYDATA, (WPARAM)hWnd1, (LPARAM)&cds);

在进程 2 的窗口过程中接收数据:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_COPYDATA:

{

COPYDATASTRUCT* pcds = reinterpret_cast(lParam);

if (pcds)

{

wchar_t* receivedData = reinterpret_cast(pcds->lpData);

// 处理接收到的数据

}

}

break;

// 其他消息处理...

}

return 0;

}

总结

Windows 消息队列是 Windows 操作系统中实现通信和交互的核心机制之一。通过深入理解消息队列的原理、消息的分类、消息循环以及消息传递的方式,开发者能够编写出更加健壮、高效且交互性良好的 Windows 应用程序。无论是简单的桌面应用,还是复杂的大型系统,掌握 Windows 消息队列的知识都将为开发工作带来极大的帮助。在实际应用中,根据不同的需求选择合适的消息传递方式和函数接口,能够优化应用程序的性能和响应速度,提升用户体验。

你可能感兴趣的:(Windows,windows)