码迷,mamicode.com
首页 > Windows程序 > 详细

windows 消息循环的那些事

时间:2015-05-09 08:57:40      阅读:180      评论:0      收藏:0      [点我收藏+]

标签:windows   ui   线程   消息机制   消息循环   

本文将结合网上的一些资料及自己的经验、见解,对windows消息机制进行简单的剖析,有不对的地方欢迎指正哈!!


首先,指明一些消息在windows系统中是什么角色:

消息是消息机制中的邮件,用于工作线程与UI线程、窗体与窗体、一个进程对另一个线程的窗体进行通讯。他是win32程序运行的血液,通过消息才能把整个系统关联起来。

消息对应于系统的一个UINT值,也即32位的无符号整形值,例如我们平时会自定义的WM_USER、WM_PAINT等等。它唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序的消息队列(下面会讲到)中,然后应用程序再从消息队列中取出消息并进行相应的响应。在这个处理的过程中,操作系统也会给应用程序“发送消息”,而所谓的发送消息--------实际上就是操作系统调用程序中的一个专门负责处理消息的函数,这个函数称为窗口过程。

这里再点明一下,像鼠标点击事件、键盘事件等这些事件需要依赖系统的系统对这些硬件信号转化为具体的消息,而这需要驱动层将这些硬件信号转化为事件通知内核,内核再转化为消息放到消息队列(后边会将)中,这也就是大家一致所说的事件机制:硬件到消息的转化。

消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,在Windows中MSG结构体定义如下:

typedef struct tagMsg
{
       HWND    hwnd;            //接受该消息的窗口句柄
       UINT    message;         //消息常量标识符,也就是我们通常所说的消息号
       WPARAM  wParam;          //32位消息的特定附加信息,确切含义依赖于消息值
       LPARAM  lParam;          //32位消息的特定附加信息,确切含义依赖于消息值
       DWORD   time;            //消息创建时的时间
       POINT   pt;              //消息创建时的鼠标/光标在屏幕坐标系中的位置
}MSG;

hwnd是指消息所属的窗体句柄(这里先点出,其实消息是属于线程的,所以通过PostThreadMessage 向指定线程发送消息的时候,这里的hwnd可以为空,表示消息不属于任何窗体)

消息队列:

系统启动时就会创建系统消息队列,用来接收硬件事件转化成的消息,再由RIT线程将事件分化到各个应用线程对应的消息队列。
每个UI线程都有一个消息队列,而不是每个窗体一个消息队列!
什么是UI线程呢?只能简单地说,当一个线程调用Win32 API中的GDI(Graphics Device Interface)和User函数时,操作系统才会将其看成是一个UI线程,并为它创建一个消息队列,这时候再添加线程的消息处理循环就可以当时一个UI线程了。具体可以参考我转载的一篇博文细说UI线程和Windows消息队列

1. 系统消息队列

当操作系统启动并初始化时,线程Raw Input Thread(RIT)就会启动,并创系统硬件输入队列(System Hardware Input Queue)(SHIQ). 对于外部的硬件事件(鼠标或者键盘),硬件驱动会将事件转换成消息,并存放到SHIQ中,而RIT线程就专门负责处理SHIQ中的消息,把消息分发到对应线程的消息队列里面。

2. 线程消息队列

对于每个用MFC开发的GUI程序,他们都有一个CMy***App,该类继承自CWinApp,而CWinApp继承自CWinThread, CWinApp是一个GUI线程,系统会为其维护一个THREADINFO结构,

 消息队列包含在一个叫THREADINFO的结构中,有四个队列:

?
1
2
3
4
Sent Message Queue     发送消息队列
Posted Message Queue   登记消息队列
Visualized Input Queue 输入消息队列
Reply Message Queue    响应消息队列

Sent Message Queue: 该队列保存其他程序通过SendMessage给该线程发送的消息 
Posted Message Queue: 该队列保存其他队列通过PostMessage给该线程发送的消息 
Visualized Input Queue: 保存系统队列分发过来的消息,比如鼠标或者键盘的消息 

Reply Message Queue: 保存向窗体发送消息后的结果,比如sendMessage操作结束后,接收消息方会发送一个Reply消息给发送方的Reply队列中,以唤醒发送队列。

 这些队列如何产生的?线程是内核对象,我们看看线程信息是如何定义的,包含哪些内容,分析如下THREADINFO结构体定义,可以得出一些信息:

技术分享


消息循环:

当一个线程有属于自己的队列时,那么线程需要有一个循环函数一直去消息队列中拿取消息,并对消息进行处理。通过vs新建的application工程中默认的主函数就包含这么一段消息循环函数(或许你会熟悉):
// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

是不是有一种似曾相识的感觉:)
GetMessage(&msg, NULL, 0, 0)
 这一句是从消息队列中获取一个消息,NULL表示只要队列里有消息就获取,也可以指定获取某个特定窗体的消息。这个函数是阻塞的,拿不到消息就会一直等,直到有消息为止。具体参见可看msdn GetMessage文档
拿到消息后就是对消息的处理了:
调用TranslateAccelerator函数对快捷键消息进行转化并发送出去,如果不成功再调用TranslateMessage函数将WM_KEYUP,WM_KEYDOWN之类的消息转化成相应的WM_CHAR之类的消息。再调用DispatchMessage将消息传给各给窗体的处理函数进行处理。
那具体的处理函数在哪里:
1、首先我们可以通过GetMessage获取到消息后自己处理
2、还记得那些年我们是怎么注册窗体类的吗?还记得注册窗体类需要传一个LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  函数吗?这个函数就是窗体消息的处理函数:
//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	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;
}

可能你会问,那上边的消息循环不是死循环吗?那我们怎么退出来?

你还记得我们是怎么关闭窗口的吗:通过调用Dialog::close() 函数其实会向窗体发送一个 WM_DESTROY消息,从上边的代码可以看到,处理这个消息的逻辑其实就是 调用PostQuiteMessage函数,这个函数将像窗体发送一个WM_QUIT消息,仔细看一下GetMessage函数的返回值可以看出,只有GetMessage拿到这个消息时会返回0,其他的都是非零值,所以此时消息循环就退出了。

那么消息是怎么根据窗体句柄找到对应的线程的呢?

由于窗体是一种特殊的句柄,属于内核资源,在创建的时候系统就把他绑定到具体的某个线程句柄了,所以你才可以通过系统函数:HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName ); 根据窗体名从内核中找到对应的句柄。而这也是为什么前边跟大家说的,消息是属于线程的,而不是属于窗体的,因为一个线程对应一个消息队列,对应多个窗体。

知道怎么收、处理消息了,那可以怎么发送消息呢?Message函数家族:

SendMessage的原型如下:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),

这个函数主要是向一个或多个窗口发送一条消息,一直等到消息被处理之后才会返回。不过需要注意的是,如果接收消息的窗口是同一个应用程序的一部分,那么这个窗口的窗口函数就被作为一个子程序马上被调用;如果接收消息的窗口是被另外的线程所创建的,那么窗口系统就切换到相应的线程并且调用相应的窗口函数,这条消息不会被放进目标应用程序队列中。函数的返回值是由接收消息的窗口的窗口函数返回,返回的值取决于被发送的消息。
     PostMessage的原型如下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),

该函数把一条消息放置到创建hWnd窗口的线程的消息队列中,该函数不等消息被处理就马上将控制返回。需要注意的是,如果hWnd参数为 HWND_BROADCAST,那么,消息将被寄送给系统中的所有的重叠窗口和弹出窗口,但是子窗口不会收到该消息;如果hWnd参数为NULL,则该函数类似于将dwThreadID参数设置成当前线程的标志来调用PostThreadMEssage函数。


  从上面的这2个具有代表性的函数,我们可以看出消息的发送方式和寄送方式的区别所在:被发送的消息会被立即处理,处理完毕后函数才会返回;被寄送的消息不会被立即处理,他被放到一个先进先出的队列中,一直等到应用程序空线的时候才会被处理,不过函数放置消息后立即返回。

  实际上,发送消息到一个窗口处理过程和直接调用窗口处理过程之间并没有太大的区别,他们直接的唯一区别就在于你可以要求操作系统截获所有被发送的消息,但是不能够截获对窗口处理过程的直接调用。
  以寄送方式发送的消息通常是与用户输入事件相对应的,因为这些事件不是十分紧迫,可以进行缓慢的缓冲处理,例如鼠标、键盘消息会被寄送,而按钮等消息则会被发送。
  广播消息用得比较少,BroadcastSystemMessage函数原型如下:
       long BroadcastSystemMessage(DWORDdwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);该函数可以向指定的接收者发送一条消息,这些接收者可以是应用程序、可安装的驱动程序、网络驱动程序、系统级别的设备驱动消息和他们的任意组合。需要注意的是,如果dwFlags参数是BSF_QUERY并且至少一个接收者返回了BROADCAST_QUERY_DENY,则返回值为0,如果没有指定BSF_QUERY,则函数将消息发送给所有接收者,并且忽略其返回值。

BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM IParam); 向具体的线程发送消息,可以不属于哪个窗体的消息。这也使得消息不仅仅是用于窗口界面的,而且用于整个系统的

MFC消息机制:
个人看法,MFC其实就是封装了一些系统消息API的,并添加一些消息定义宏的库,详细资料可以参考博文MFC消息机制

写这个主要是用于个人记笔记、并锻炼写文章的能力,有问题请轻喷:)





windows 消息循环的那些事

标签:windows   ui   线程   消息机制   消息循环   

原文地址:http://blog.csdn.net/yangyihongyangjiying/article/details/45584835

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!