代码改变世界

《Windows游戏编程大师技巧》二、Windows编程模型

2012-04-14 15:26  htc开发  阅读(253)  评论(0编辑  收藏  举报


这一章开篇语对我有很大的启迪,一直对Windows下编程有着畏难心理。太庞大的平台和体系,
太多的API和术语,太快的技术换代节奏,而我是个想要把握住本质掌握住基础才会有安全感
和成就感的程序员,所以我只乐于学习Win32 API。而现在受到这本书的启发,对Windows编程
的畏惧少了一些,也有了一些学习计划:

1.窗体界面 -> Win32\MFC (简单了解界面消息处理机制吧,实在是不擅长画UI,MFC也有些难)
2.网络编程 -> Winsock (这是我比较想学的)
3.Web控件 -> ActiveX (不了解,应该学学)
4.动画游戏 -> GDI\DirectX (感兴趣,想写点小游戏自娱自乐)

所以我觉得写个小游戏作为业余项目是很不错的,游戏部分可以学学DirectX,设置界面可以简单
学习下MFC,然后再给游戏增加个网络对战功能就可以学到Winsock了。业余项目关键是不要追求
完美,先把基本功能完成,基础框架搭好。曾经想用Java2D写个塔防游戏,就是太想一下子就写好,
于是就半途而废了。

========================================================

Windows编程就像去见牙医:虽然明明知道对自己是有益处的,可还是没人喜欢总是找牙医。
虽然我不能保证在阅读本章之后你会变得更加喜欢去见牙医,但是我敢保证你会比以往更喜欢
Windows编程。别因为我要解放你的思想而感到害怕(特别是钟情于DOS的顽固分子)。


Windows的起源

Windows 1.0完全建立在DOS基础上,不能执行多任务,运行缓慢。Windows 3.0让开发人员和
商业应用程序逐渐脱离DOS。但游戏编程的人们还在唱着“坚守DOS岗位直到炼狱冻结”的赞歌。
Windows 95推出后,我才真正开始喜欢Windows编程。

Windows 9X/NT是多任务、多线程的,这和DOS有很大不同。DOS下,一旦你的程序开始运行,
就只能运行该程序(中断处理程序除外)。要想多线程就必须自己模拟,这也是游戏程序员多年
来所做的事。

Windows还是一个事件驱动的操作系统。我们不必关心其他正在运行的应用程序,Windows会去处理,
我们只要关心自己的应用程序中信息的处理。这在Windows 3.0/3.1中是不可能的,它们并不是真正的
多任务系统,每一个应用程序都要产生下一个程序。



匈牙利符号表示法

像微软一样的公司,有几千个程序员在干不同的项目,一个名叫Simonyi的人负责创建了一套
编写微软代码的规范。所有微软的API、界面、技术文件等等都采用这些规范。这个规范被称为
匈牙利符号表示法,也许因为他经常加班弄得饥肠辘辘吧(饥饿Hungry和匈牙利Hungary)。



1.变量:根据变量类型采用不同的前缀,之后的每个子名都要以大写字母开头。如char *szFileName;
          全局变量还要在开头加上g_或g,如int g_iXPos;

2.函数:与变量相同,但没有前缀。如int PlotPixel(int ix, int iy, int ic);

3.类型和常量:所有字母大写,子名可以用下划线分隔。如#define MAX_CELLS 64

4.类:所有C++的类以大写C为前缀,其余与变量相同。如class CVector { ... };

5.参数:与变量命名相同,如GetPixel(int ix, int iy); 但GetPixel(int x, int y);也是可以的。

学会阅读匈牙利符号表示法但不代表要改变你自己的编程风格。本书在使用Win32 API函数的场合
使用匈牙利表示法,而在其他位置使用自己的风格。


世界上最简单的Windows程序

基于DOS的“Hello World”程序。注意:要用VC创建一个控制台应用程序,
这是类DOS的应用程序,只是它是32位的。将目标.EXE设为控制台应用
程序,而非Win32.EXE!


Windows程序都以WinMain()开始,就像DOS都以main()开始一样。



现实中的Windows程序

我们所需的只是一个基本的Windows程序,打开一个窗口、处理信息、调用游戏主循环等。
因此只要进行下列工作:
     1. 创建Windows类。
     2. 创建事件句柄。
     3. 注册Windows类。
     4. 用之前的Windows类创建一个窗口。
     5. 创建一个能够从事件句柄获得或向事件句柄传递信息的主循环。

1.创建Windows类

Windows中的每一个窗口、控件、列表框、对话框和小部件都是一个Windows类。你也可以
自定义Windows类,你编写的每一个应用程序都应该至少有一个Windows类,否则会非常麻
烦。控制Windows类的数据结构是:WNDCLASSEX。创建Windows类就是设置好它的各种属性。

WNDCLASSEX winclass = {
          sizeof(WNDCLASSEX),
          CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
          WindowsProc,
          0,
          0,
          hInst,
          LoadIcon(NULL, IDI_APPLICATION),
          LoadCursor(NULL, IDC_ARROW),
          (HBRUSH) GetStockObject(BLACK_BRUSH),
          NULL,
          "WINCLASS1",
          LoadIcon(NULL, IDI_APPLICATION)
     };

2.事件句柄

由上图看到一个重要的属性 - 回调函数,它是通过lpfnWndProc设定。
来看一个事件处理函数的例子,在这里我们只关心WM_CREATE\PAINT\DESTROY三个事件。

LRESULT CALLBACK WindowsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC hdc ;
     PAINTSTRUCT ps ;
     RECT rect ;
     switch (message)
     {
          case WM_CREATE:               
               return 0 ;
          case WM_PAINT:
               hdc = BeginPaint (hwnd, &ps) ;               
               EndPaint (hwnd, &ps) ;
               return 0 ;
          case WM_DESTROY:
               PostQuitMessage (0) ;
               return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

3.注册Windows类

很简单只需一行代码:RegisterClassEx(&winclass);
还有一个旧版本的RegisterClass()用于注册基于旧结构WNDCLASS类。
一旦注册就可以任意创建它的窗口了。

4.创建窗口

     hwnd = CreateWindowEx(
                         NULL,                         // extended style
                        "WINCLASS1",               // class
                         "Your Basic Window",     // title
                        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                        0, 0, 400, 400,               // initial x/y/width/height
                        NULL,                         // parent window handle
                        NULL,                         // window menu handle
                        hInst,                         // program instance handle
                        NULL);                         // creation parameters

如果上面没有设置WS_VISIBLE则需要手动调用ShowWindow来让窗口显示。
调用UpdateWindow触发WM_PAINT事件。
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;

5.主事件循环

while (GetMessage (&msg, NULL, 0, 0))
{
     TranslateMessage (&msg) ;
     DispatchMessage (&msg) ;
}

非常简单。GetMessage是关键,用途是从事件队列中获得消息,保存到MSG结构中。
MSG结构包含了WinProc参数列表中的所有参数,之后调用TranslateMessage稍加处理
和转换,并通过DispatchMessage调用WinProc进行进一步处理。

一个给力的图示,包含了所有事件处理流程!



==============================================

2012年4月22日 更新

1.创建WNDCLASSEX时指定了类名为"WINCLASS1",之后注册它,并在创建窗体时指定了类名"WINCLASS1",
所以就会关联到WNDCLASSEX,并创建出它指定样式大小的窗体。而在创建其他预定义控件时,如按钮控件,
"button"类已经预定义并注册了,我们只需在调用CreateWindowEx指定类名为"button"。

2.GetMessage()和PeekMessage()的区别:它们都是从消息队列中取消息。当消息队列中没有消息时,GetMessage()
会阻塞当前线程,而PeekMessage()直接返回不会阻塞。并且PeekMessage()只取消息不从队列中删除,取出并删除
消息需要指定PM_REMOVE参数:PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)。

3.MSG结构:
     typedef struct tagMSG {
          HWND hwnd;          // window where message occurred
          UINT message;       // message id itself
          WPARAM wParam;   // sub qualifies message
          LPARAM lParam;      // sub qualifies message  
          DWORD time;          // time of message event
          POINT pt;              // position of mouse
     } MSG;

MSG结构的前四个成员变量正是WinProc函数的四个参数。验证了WinMain主循环中GetMessage()取到MSG结构后,
调用DispatchMessage()从MSG中取出参数并触发WinProc进行进一步处理。

4.wParam和lParam在不同的消息中会保存不同的值。在按钮控件的消息中,msg为WM_COMMAND,LOWORD(wParam)
为子窗口ID(创建按钮时指定),HIWORD(wParam)为通知码(1 - BN_CLICKED,2 - BN_DOUBLECLICKED),lParam为
子窗口句柄。