现在的位置: 首页 > 综合 > 正文

Windows消息机制2

2013年10月08日 ⁄ 综合 ⁄ 共 4611字 ⁄ 字号 评论关闭

 

Windows是消息驱动的,从设计上来说,通过消息可以减少耦合,增加结构的灵活性。

              

一个典型的Windows程序是这样的:

              

程序在启动时创建了一个或多个窗口,在创建窗口时指定了窗口的消息处理函数:

                  WNDCLASSEX wndclass;

                  wndclass.lpfnWndProc=WindowProc; // WindowProc就是处理消息的函数名

                  ...

                  RegisterClassEx(&wndclass);

               ...

              

然后程序进行消息循环,不断的接收消息,并且将消息分发到不同的窗口上:

               // 主消息循环:

               while (GetMessage(&msg, NULL, 0, 0))

               {

               if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

               {

               TranslateMessage(&msg);

               DispatchMessage(&msg);

               }

               }

               在窗口的消息处理函数中,对不同的消息进行处理:

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

               {

               switch(message)

               {

               case 消息1:

               ... // 处理过程

               case 消息2:

               ... // 处理过程

               default:

               return DefWindowProc(hWnd, message, wParam, lParam);

               }

               }

              

整个程序就是这样不断的接收消息、分发消息、处理消息,只到出现WM_QUIT消息,整个程序然后退出。了解这个程序背后的东西,可以有助于我们更了解Windows的消息机制。

               

(一)消息的结构,一个消息的组成分成四个部分:

                  HWND hWnd:消息发送的目的窗口

                  UINT message:标识是什么种类的消息,在消息的种类中,0到WM_USER–1是系统消息,而一般用户自定义的消息从WM_USER开始。

                  WPARAM wParam:消息的参数,这是与消息的种类相关的,即不同的消息,这里的含义也不一样

                  LPARAM lParam:消息的参数

kingofcoder.com

              

(二)消息的产生及发送

              

消息的产生一般是由Windows自动根据用户的动作(如击键,鼠标的动作等等)产生的,也可以在程序中编码产生特定的消息。

              

系统全局有一个消息队列,Windows先将消息放在这个队列中,然后再根据消息的信息(如当前的活动窗口)分发到不同的窗口的消息队列中。

              

不过严格说来,消息队列是依附在线程上的,而不是窗口上,也就是说,一般我们认为的窗口的消息队列其实就是创建这个窗口的线程的消息队列。如果一个线程创建了多个窗口,那么那几个窗口就共享一个消息队列,这时消息结构中的hWnd,就指明了消息是哪个窗口的。

              

当然,并不是所有的线程都有消息队列,只有使用了User或GDI方法的线程才有消息队列。MSDN中的原文是这样:

               The system maintains a single system message queue and one thread-specific message queue for each graphical user interface (GUI) thread. To avoid the overhead of creating a message queue
for non–GUI threads, all threads are created initially without a message queue. The system creates a thread-specific message queue only when the thread makes its first call to one of the User or Windows Graphics Device Interface (GDI) functions.

kingofcoder.com

              

在线程中创建了窗口,这个窗口就有了消息队列,不过有时我们希望线程不创建窗口也有消息队列,具体怎么做,下面会有例子来示范一下。

              

如果是在程序中手工发送消息,可以使用:

              

BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)方法向hWnd指定的窗口发送消息,即将消息放在那个窗口的线程的消息队列中,然后立即返回。

              

              

LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)则直接将消息送到hWnd指定的窗口WindowProc函数进行处理,

              

处理完后才返回。也就是说,不是所有的消息都必须经过消息队列,这种消息叫就Nonqueued Messages。

              

因为SendMessage的特性,可能会出现消息死锁:Message Deadlocks,可以想象一下,大家相互SendMessage会怎么样。具体怎么做,参考一下MSDN吧,详细的这里不说了,不过一般情况下,只要设计时细心点,是不会出现死锁的。

              

上面的方法中程序发送消息到“本线程”的消息队列,也可以发送消息到“其它线程”的消息队列,这时使用的方法是PostThreadMessage,不过注意,不是所有的线程都有消息队列,因此这个PostThreadMessage方法可能会失败。

kingofcoder.com

              

消息的产生还可以采用广播的方式,即一次性发给多个窗口:

               long BroadcastSystemMessage(DWORD dwFlags, LPDWORD lpdwRecipients, UINT uiMessage, WPARAM wParam, LPARAM lParam);

               这里lpdwRecipients指定接收者。

kingofcoder.com

              

(三)接收和分发消息

               再看一下前面提到的典型的主消息循环:

               while (GetMessage(&msg, NULL, 0, 0))

               {

               if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

               {

               TranslateMessage(&msg);

               DispatchMessage(&msg);

               }

               }

               其中:

               GetMessage方法是从消息队列中取消息,取一个删一个,如果没有消息就进行等待。

              

也可以使用PeekMessage方法,作用与GetMessage方法一样,只是如果没有消息,程序并不等待,而是直接返回。PeekMessage也可以通过参数来指定是否从消息队列中删除消息,如:

               while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) // 这里PM_REMOVE表示从队列中删除消息,也可以选择不删除

              

GetMessage和PeekMessage方法还可以选择消息的过滤,即只取某些“类型”的消息,具体参看API文档。可见消息的处理并不完全是FIFO的。

              

TranslateMessage是进行消息的转换,如把virtual-key表示的原始键盘消息转换成更容易理解的WM_CHAR消息,这里可以看成是消息的预处理。

              

DispatchMessage方法是让Windows把这个消息送到WindowProc进行处理。这里要提一句,由于在原理上消息队列是依附于线程的,因此消息并不是必须发给具体的窗口(比如有些线程没有创建窗口,也就无法将消息发给窗口),如在PostMessage时不指定hWnd,这时消息就会被发送到线程的消息队列,不过这时就无需通过DispatchMessage来将这个消息分发到窗口的WindowProc,这时消息循环自己必须处理这个消息(当然如果不处理,这个消息就丢失了)。

kingofcoder.com

              

(三)消息处理

               一般情况下消息最终是交由窗口的WindowProc函数来处理,在WindowProc的实现中,对一些我们不需要处理的消息,MSDN手册上要求调用

               return DefWindowProc(hWnd, message, wParam, lParam);

               即让操作系统来处理这些消息,这是因为有很多消息,在大多数情况下系统缺省处理就可以了,如窗口的active,窗口的最大化最小化等等,如果不写这一句,就会丢失掉很多窗口缺省的行为。

              

有一点需要注意的就是,WindowProc函数是在注册窗口时指定的,而一个窗口类是可以实例化成多个具体的窗口的,因此WindowProc可能要处理多个窗口的消息

              

这里只是一个大概的Windows的消息“机制”,当然如果要写出更地道的windows程序,需要更深入了解具体的Windows中的不同消息(如WM_PAINT, WM_DESTROY)是如何产生的,以及如何被处理的。

kingofcoder.com

              

抱歉!评论已关闭.