本文将结合网上的一些资料及自己的经验、见解,对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;
当操作系统启动并初始化时,线程Raw Input Thread(RIT)就会启动,并创系统硬件输入队列(System Hardware Input Queue)(SHIQ). 对于外部的硬件事件(鼠标或者键盘),硬件驱动会将事件转换成消息,并存放到SHIQ中,而RIT线程就专门负责处理SHIQ中的消息,把消息分发到对应线程的消息队列里面。
对于每个用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给该线程发送的消息
Reply Message Queue: 保存向窗体发送消息后的结果,比如sendMessage操作结束后,接收消息方会发送一个Reply消息给发送方的Reply队列中,以唤醒发送队列。
这些队列如何产生的?线程是内核对象,我们看看线程信息是如何定义的,包含哪些内容,分析如下THREADINFO结构体定义,可以得出一些信息:
// 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文档
// // 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 ); 根据窗体名从内核中找到对应的句柄。而这也是为什么前边跟大家说的,消息是属于线程的,而不是属于窗体的,因为一个线程对应一个消息队列,对应多个窗体。
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,则函数将消息发送给所有接收者,并且忽略其返回值。
原文地址:http://blog.csdn.net/yangyihongyangjiying/article/details/45584835