Windows消息队列及C++应用

1. Windows消息机制

Windows是一个消息驱动的操作系统,消息是用一个常量标识符来标记,并且有两个32Bit的消息附加信息。单击鼠标、敲击键盘,都会通过电脑外设向系统发送特定的中断信号,这个中断信息在操作系统中会转化为一个消息,并存储在系统的一个消息队列中。Windows操作系统会根据当前激活的窗口与鼠标、键盘的操作来决定将消息发给相应的窗口线程。
在这里插入图片描述

2. Windows消息队列

Windows操作系统会为每个线程维护一个消息队列,这个消息队列是操作系统维护的,即存在于内核中。内核对象对每个进程都是可见的,即消息队列可以用于进程间通信。
为了提升效率,操作系统并不会立即为每个线程创建消息队列,而是在调用PeekMessage/GetMessage时才创建消息队列。

3. Windows消息传递机制

从消息的发送途径来看,Windows程序中的消息可以分成2种:队列消息和非队列消息,也有叫“进队消息”和“不进队消息”。

3.1. 队列消息

3.1.1. 发送消息

队列消息主要是用来存储通过PostMessage和PostThreadMessage发送的消息,前者是用来发给指定窗口线程,后者则是通过指定线程ID(线程是由内核创建的,线程ID也即是内核唯一)来发送的。

3.1.2. 接受消息

PeekMessage/GetMessage可以从消息队列中取出消息,PeekMessage查询是否有消息,有则取出并返回成功,无则返回失败。GetMessage则是阻塞式查询,一直等到有消息才返回。

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		// TranslateAccelerator处理快捷键消息,如果不是快捷健消息返回失败
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg); // 将虚拟键盘消息转换为WM_CHAR字符消息
			DispatchMessage(&msg);  // 转发给窗口过程回调函数WndProc。
		}
	}

上述代码,也可以转换为下面非阻塞的调用方式

	// Main message loop:
	while (TRUE)
	{
		// 查询消息,如果有则从队列中将消息弹出
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			// TranslateAccelerator处理快捷键消息,如果不是快捷健消息返回失败
			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
			{
				TranslateMessage(&msg); // 将虚拟键盘消息转换为WM_CHAR字符消息
				DispatchMessage(&msg);  // 转发给窗口过程回调函数WndProc。
			}
		}
	}

3.2. 非队列消息

非队列消息是通过SendMessage来发送给指定窗口线程,并且立即由窗口过程函数来响应。
SendMessage是阻塞的,必须等待指定的窗口过程函数处理完之后才能返回。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: Add any drawing code here...
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

4. 非UI线程消息队列

非UI线程,因为没有窗口,所以没有句柄,发送消息只能通过PostThreadMessage。取出消息,可以使用PeekMessage/GetMessage。

4.1. 控制台消息队列示例

  • 控制台工程
#include <iostream>
#include <Windows.h>

using namespace std;

int main()
{
    MSG msg;
    while (::GetMessage(&msg, NULL, 0, 0))
    {
        switch(msg.message)
        {
        case WM_USER + 100:
            cout << "Hello World" << endl;
            break;
        default:
            break;
        }
    }

    return 0;
}
  • 测试工程
    新建测试工程,通过CreateProcess打开console.exe并获取主线程ID,然后通过PostThreadMessage发消息。
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

TCHAR szCommandLine[] = TEXT("console.exe");

if(!CreateProcess(NULL,           // No module name (use command line)
    szCommandLine,  // Command line
    NULL,           // Process handle not inheritable
    NULL,           // Thread handle not inheritable
    FALSE,          // Set handle inheritance to FALSE
    0,              // No creation flags
    NULL,           // Use parent's environment block
    NULL,           // Use parent's starting directory 
    &si,            // Pointer to STARTUPINFO structure
    &pi)            // Pointer to PROCESS_INFORMATION structure
    ) 
{
    return;
}

// 给控制台线程发送消息
::PostThreadMessage(pi.dwThreadId, WM_USER+100, 0, 0);

// Close process and thread handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

4.2. 子线程窗口消息

例如,要做一个监控USB设备插入拔出的模块,并获取USB设备的相关信息。此模块会创建一个新线程,等待DEVICE_CHANGE消息。按照一般的方式,需要通过PostThreadMessage将UI模块中响应的DEVICE_CHANGE消息转发给USB模块线程。这种方式会增加USB模块与UI模块之间的耦合。有没有更简单的方式,可能让USB模块自己也能直接获取DEVICE_CHANGE消息呢?针对这种需求,可以考虑使用Hook消息机制,即在USB模块Hook想要的消息,这样就不用通过UI转发消息了。
SetWindowsHookEx即用来HookWindows的消息,可以Hook同进程的,也可以Hook跨进程的,可以通过第3个参数控制。Hook的类型不同,其消息结构体也不同。

  • CWPSTRUCT对应WH_CALLWNDPROC
  • CWPRETSTRUCT对应WH_CALLWNDPROCRET
  • KBDLLHOOKSTRUCT对应WH_KEYBOARD_LL
// UI线程
	DWORD dwId = GetCurrentThreadId();
	AfxBeginThread(ThreadFun, (LPVOID)dwId);
	
// USB模块
LRESULT CALLBACK DeviceChangeFun(int nCode, WPARAM wParam, LPARAM lParam)
{
	CWPSTRUCT* pMsg = (CWPSTRUCT*)lParam;
	CString strLog;
	if (pMsg->message == WM_DEVICECHANGE)
	{
		OutputDebugString("---------------USB Device Change-------------------------------\n");
	}

	return CallNextHookEx(NULL, nCode, wParam, lParam);
}

unsigned int __stdcall ThreadFun(PVOID lpParam)
{
	DWORD dwId = (DWORD)lpParam;

	HHOOK mouseHook = SetWindowsHookEx(WH_CALLWNDPROC , DeviceChangeFun, 0, dwId);
	MSG msg;
	while (GetMessage(&msg, NULL, NULL, NULL))
	{
	}
	UnhookWindowsHookEx(mouseHook);

	return 0;
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值