API消息处理过程

 

相信API大家应该不陌生了,不过用纯API编写Windows窗口的人可能很少了,因为他看上去似乎太麻烦了,为了开发的效率,所以有了IDE编程工具,如VC、C#、Delphi等,他们都大大的为我们的窗口程序开发提高了效率,你可以在不知道这些窗口组件是怎么创建的就能很好的使用它,这就是现代化编程带来的好处,同时对新人来讲也是有一定弊处的,因为你总是依赖这些表面的东西,而原理性的问题却不清楚,碰到一些小问题就可能止步不前了,就像一个没有异常处理功能的程序一样,一个小小的异常问题就能让你的程序崩溃终止,这可就阻碍我们想成为一个优秀程序员的梦想了。所以为了成为优秀的程序员,我们有必要将这些IDE背后的黑手一一抓出来。所谓擒贼先擒王嘛!嘿嘿!

 

   这篇算第一节吧!介绍一个纯 API 打造的 Windows 程序流程。

 

   再介绍前我们先来简单了解下Windows消息机制,因为如果不了解Windows消息处理机制,我们就无法深入Windows编程。

 

   Windows消息机制处理的三大部分:

 

   1:系统消息反应堆,是Windows内核消息机制的源动力,它支配着所有消息的有顺传递,对于每个正在执行的Windows应用程序,系统会为其创建一个应用程序的消息队列,用来存放该程序可能创建的各种窗口的消息。

 

   2:应用程序消息泵,是通过应用程序中一块称之为“消息循环”的代码,从对应的应用程序消息队列池中取出消息,并把它们分发到相应的窗口函数中处理。

 

   3:窗口消息处理器,是负责处理由消息泵发来的各种关于此窗口对象操作的消息(即窗口消息处理的回调函数,或叫窗口消息响应代码)。

 

   如果把系统消息反应堆比作是地下水资源,那应用程序消息泵就是我们的抽水机了,而窗口消息处理器就好比我们需要灌溉的地,然后由这些地种出各种各样的植物来。当然上面的称谓是经我包装过的,这也许会对其它同学造成一定的打击!但为了能更生动的描述这个抽象的问题,还望各位大大们多多包含。嘻嘻!:)。

 

   当然系统消息反应堆故不受我们的直接控制,他由系统管理,这个我们不必操心,我们只需要用CreateWindow/CreateWindowEx函数创建一个窗口,系统会自动为我们创建一个对应的应用程序消息队列,供我们的程序使用。如果你创建的窗口不是Windows预先注册的(预先注册的包括Buttons、Static 、Edit、 List Boxes等),那么你还需要用RegisterClass/RegisterClassEx函数向Windows操作系统先注册你的窗口类型。如果注册成功,就会返回这个窗口类型的标识号,然后你就可以用标识号进行创建窗口,查找窗口和注销窗口类型等等。如下代码所示:

 

   HINSTANCE hInstance=GetModuleHandleA(NULL);

 

   RegisterMyClass(hInstance);

 

   ATOM RegisterMyClass(HINSTANCE hInstance)

   {

      WNDCLASSEX winclass;

 

      winclass.cbSize        = sizeof(WNDCLASSEX);          // 本结构的字节大小

      winclass.style         = CS_HREDRAW | CS_VREDRAW;     // 窗口样式

      winclass.lpfnWndProc   = WndProc;                     // 窗口处理消息的回调函数

      winclass.cbClsExtra    = 0;                           // 窗口类型的扩展

      winclass.cbWndExtra    = 0;                           // 窗口实例的扩展

      winclass.hInstance     = hInstance;                   // 窗口实例句柄

      winclass.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINICO)); // 窗口图标,如果为NULL系统会默认一个给你

      winclass.hCursor       = LoadCursor(NULL, IDC_ARROW); // 窗口的光标,如果为NULL系统会默认一个给你

      winclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);    // 窗口背景颜色

      winclass.lpszMenuName = NULL;                        // 窗口菜单名称

      winclass.lpszClassName = "FirstClass";                // 窗口类型的名称

      winclass.hIconSm       = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SMICO)); // 窗口小图标,如果为NULL系统会默认一个给你

 

      return RegisterClassEx(&winclass);                    // 注册前面描述窗口类型

   }

 

   注意hInstance是一个程序实例句柄(模块句柄),他由GetModuleHandleA(NULL)取得当前应用程序的模块句柄,它的作用一般用于在取模块资源时提用(也就是说你要取的资源是基于哪里的,可以这么理解吧!)。注册好之后就可以使用CreateWindow/CreateWindowEx函数实例化刚刚注册的窗口类型了,当然如果失败它则会返回的空值。如下

 

   hWnd = CreateWindowEx(WS_EX_WINDOWEDGE|WS_EX_CLIENTEDGE, // 扩展的窗口类型

             "FirstClass",                                  // 已注册的窗口类型名称

             "FirstWindows",                                // 窗口名称

             WS_OVERLAPPED|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION, //窗口样式

             100,                                           // 窗口左上角X位置

             100,                                           // 窗口左上角Y位置

             800,                                           // 窗口的宽

             600,                                           // 窗口的高

             NULL,                                          // 窗口的父窗口句柄,即是否有父窗口

             NULL,                                          // 窗口的主菜单

             hInstance,                                     // 应用程序的实例句柄

             NULL);                                         // 传送给窗口的自定义参数

 

 

   正常情况下通过上面的操作就可以建立了一个窗口了,在调用CreateWindowEx成功时我们的WndProc窗口处理回调函数会马上先后收到如下系统发来的消息。

 

   msg=0x24 WM_GETMINMAXINFO        // 将要改变窗口大小或位置

   msg=0x81 WM_NCCREATE             // 当窗口第一次被创建时,此消息在WM_CREATE消息发送前发送

   msg=0x83 WM_NCCALCSIZE           // 核算窗口的客户区域

   msg=0x01 WM_CREATE               // 应用程序创建一个窗口

 

 

   应用程序消息泵则是我们窗口程序中用来提取消息队列的一段消息循环代码,他的作用就是取出系统投递给我们的应用程序消息,以便我们的窗口程序作出适当的响应。他一般由如下代码形式组成。

 

   while(GetMessage (&msg, NULL, 0, 0)) // 或者是 PeekMessage   

   {       

      TranslateMessage (&msg) ;        // 进行一些键盘转换

      DispatchMessage (&msg) ;         // 将消息传递给相应的窗口消息处理函数

   }

 

   这就是一个典型的“消息循环”代码(即应用程序消息泵),程序的消息由GetMessage或者PeekMessage函数从消息队列中取得,然后通过DispatchMessage函数将消息分发到相应窗口对象的处理函数中处理(实际上DispatchMessage函数是将msg回传给Windows,然后再由Windows将该消息发送给适当的窗口消息处理函数进行处理,即窗口消息处理器中处理。)。

 

   这里GetMessage函数的第一个参数指定要接收消息的MSG结构的地址,第二个参数表示窗口句柄,一般将其设置为空,表示要获取该应用程序创建的所有窗口的消息;第三、四参数用于指定消息范围。后面三个参数被设置为默认值,用于接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()返回TRUE;如果GetMessage收到一个WM_QUIT消息,则返回FALSE以退出消息循环,终止程序运行。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。另外PeekMessage函数只比GetMessage一个参数,它是用来指示取出消息时是否删除应该消息。两者最大的区别就是GetMessage是以阻塞方式运行,而PeekMessage是以非阻塞方式运行,后者在现代的编程用得最多,VC、DELPHI等都用这个,早期认为PeekMessage比较浪费CPU资源,不过后面证明似乎没什么太大的区别,嘿嘿!。

 

   而TranslateMessage函数只是用来把虚拟键消息转换为字符消息,其并不会修改原有的消息,当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而不我们想要ASCII码字符,通过调用TranslateMessage这个函数后就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(即将包含了ASCII码的字符附加转化后的消息的wParam参数上),然后它会将转换后的新消息投递到调用线程的消息队列中(也就是我们的消息泵线程),最后在下一次调用GetMessage或PeekMessage函数时从该应用程序的消息队列中读出。

 

   这里值得注意的是前面用CreateWindowEx创建窗口时WndProc会收到窗口创建时的消息,而此消息并不是由应用程序消息泵发来的,而是CreateWindowEx告诉系统我将要创建窗口时,由系统消息反应堆直接发来的,并在窗口创建成功的第一时间用消息通知我们窗口已经创建完毕。

 

 

   窗口消息处理器,也可以叫窗口消息处理回调函数,他是在CreateWindowEx创建窗口前RegisterClassEx注册窗口类时就指定的消息处理回调函数,如前面的WndProc函数。典形的WndProc函数如下

 

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

   {

      switch (message)

      {

        case WM_DESTROY:     // 窗口被销毁时

            MessageBox(hWnd, "程序将要退出了哟!", "提醒!", MB_OK);

            PostQuitMessage(0);

            break;

        default:

            return DefWindowProc(hWnd, message, wParam, lParam); // 将不处理的消息传给系统处理

      }

      return 0;

   }

 

   当窗口显示之后,它就会源源不断地接收到需要处理的用程序消息。如果WndProc函数不处理这个消息,就可以把它转向DefWindowProc函数来处理,这时系统会用窗口默认消息处理函数。当你按下菜单,或者点击窗口时,窗口需要运行这个消息处理函数,否则你可能就看到不窗口响应操作的效果了。

 

   备注:此外在CreateWindowEx成功后,也可以用SetWindowLong函数为窗口增加新的消息处理回调函数。消息处理回调函数的触发顺序总是后来先得(即新挂增的消息处理回调函数会第一个拿到消息处理的控制权。)。等待处理完后再将消息由DefWindowProc函数传给那个窗口的下一个消息处理回调函数中处理(或者处理后就直接返回,终止消息再往下传,也就是说排在它后面的其它处理回调函数就收不到此消息了。嘻嘻!很坏吧!)。

 

   这里介绍的是一个WINDOWS窗口的最基本框架,创建后只有一个白白的窗口,还有一个能响应退出响应的处理。

 

   API创建窗口流程:

   1:RegisterMyClass    // 注册窗口类

   2:CreateWindowEx    // 创建已经注册的窗口(必须)

   3:GetMessage/PeekMessage   // 消息循环-->获取消息(必须)

   4:TranslateMessage     // 消息循环-->转换虚拟键消息(当你的程序不需要这些东西时,可略!)

   5:DispatchMessage    // 消息循环-->将消息传递给相应的窗口消息处理函数(必须)

   6:DefWindowProc    // 消息处理回调函数体内-->>将不处理的消息传给系统处理(必须)

你可能感兴趣的:(API消息处理过程)