第2章 Windows编程模型
"Lilu Dallas Multipass!"
—Lilu, The 5th Element(电影《第五元素》)
Windows编程就像去见牙科医生:虽然明明知道对自己是有益处的,可还是没人喜欢总是找牙医。对不对?在本章中,我将要使用“禅”的方法——或者换句话说,就是深入浅出地向你介绍基本的Windows编程。虽然我不能保证在阅读本章之后你会变得更加喜欢去见牙医,但是我敢保证你会比以往更喜欢Windows编程。下面是本章的内容:
? Windows的历史
? Windows的基本风格
? Windows的类(Class)
? 创建窗口
? Windows事件句柄(Event Handler)
? 事件驱动编程和事件循环
? 打开多个窗口
Windows的起源
别因为我要解放你的思想而感到害怕(特别是钟情于DOS的顽固分子)。让我们迅速浏览一下Windows这些年的形成和发展,以及它与游戏开发界的关系,好吗?
早期版本的Windows
Windows的发展始于Windows 1.0版本。这是Microsoft公司商业化视窗操作系统的第一次尝试,当然它是一个相当失败的产品。Windows 1.0完全建立在DOS基础上(这就是一个错误),不能执行多任务,运行速度很慢,看上去也差劲。它的外观可能是其失败的最重要原因。除了讽刺以外,问题还在于Windows 1.0与那个时代的80286计算机(或更差的8086)所能提供的相比需要更高的硬件、图像和声音性能。
然而,Microsoft稳步前进,很快就推出了Windows 2.0。我记得获得Windows 2.0的Beta测试版时我正在Software Publishing Corporation工作。在会议室中,挤满了公司的各级主管,也包括公司总裁(像往常一样,他正端着一杯鸡尾酒)。我们运行Windows 2.0 Beta演示版,装载了多个应用程序,看上去似乎还说得过去。但是,那时IBM已经推出了PM。PM看上去要好得多,而且是建立在比Windows 2.0先进得多的操作系统OS/2的基础上的。而Windows 2.0依然是基于DOS的视窗管理器。那天董事的结论是:“不错,但还不是一个可继续开发的操作系统。让我们继续开发DOS程序好了,给我再来一杯鸡尾酒怎么样?”
Windows 3.x
1990年,各星系的行星终于结盟了,因为Windows 3.0问世了,而且表现酷毙!尽管它仍然赶不上 Mac OS的标准,但是谁还在意呢?(真正的程序员都讨厌Mac)。软件开发人员终于可以在PC机上创建迷人的应用程序了,而商用应用程序也逐渐脱离DOS。这成了PC机的转折点,终于将Mac完全排除在商用应用程序之外了,而后也将其挤出桌面出版业(那时,Apple公司每5分钟就推出一种新硬件)。
尽管Windows 3.0工作良好,却还是存在许多的问题、软件漏洞,但从技术上说它已是Windows 2.0之后的巨大突破,有问题也是在所难免。为了解决这些问题,Microsoft推出了Windows 3.1,开始公关部和市场部打算称之为Windows 4.0,但是,Microsoft决定只简单地称之为Windows 3.1,因为它还不足以称之为升级的换代版本。它还没有做到市场部广告宣传的那样棒。
Windows 3.1非常可靠。它带有多媒体扩展以提供音频和视频支持,而且它还是一个出色的、全面的操作系统,用户能够以统一的方式来工作。另外,还存在一些其他的版本,如可以支持网络的Windows 3.11(适用于工作组的Windows)。惟一的问题是Windows 3.1仍然是一个DOS应用程序,运行于DOS扩展器上。
Windows 95
另一方面,从事游戏编程的人们还在唱着“坚守DOS岗位直到炼狱冻结!”的赞歌,而我甚至都焚烧了一个Windows 3.1的包装盒!但是,1995年炼狱真的开始冷却了——Windows 95终于推出。它是一个真正32位的、多任务、多线程的操作系统。诚然,其中还保留了一些16位代码,但在极大程度上,Windows 95是PC机的终极开发和发布平台。
(当然,Windows NT 3.0也同时推出,但是NT对于大多数用户来讲还是不可用的,因此这里也就不再赘述。)
当Windows 95推出后,我才真正开始喜欢Windows编程。我一直憎恨使用Windows 1.0、2.0、3.0和3.1来编程,尽管随着每一种版本的推出,这种憎恨都越来越少。当Windows 95出现时,它彻底改变了我的思想,如同其他被征服的人的感觉一样——它看上去非常酷!那正是我所需要的。
提 示
游戏编程中最重要的事情是游戏包装盒设计得如何,发给杂志公布的游戏画面如何。发送免费的东西给审阅人也是个好主意。
因此几乎是一夜间,Windows 95就改变了整个计算机行业。的确,目前还有一些公司仍然在使用Windows 3.1(你能相信吗?),但是Windows 95使得基于Intel的PC成为除游戏之外的所有应用程序的选择。不错,尽管游戏程序员知道DOS退出游戏编程行业只是一个时间的问题了,但是DOS还是他们的核心。
1996年,Microsoft公司发布了Game SDK(游戏软件开发工具包),这基本上就是DirectX的第一个版本。这种技术仅能在Windows 95环境下工作,但是它实在是太慢了,甚至竞争不过DOS游戏(如DOOM和Duke Nukem等)。所以游戏开发者继续为DOS32开发游戏,但是他们知道,假以时日,DirectX必定能具有足够快的速度,从而能使游戏流畅地运行在PC机上。
到了3.0版,DirectX的速度在同样计算机上已经和DOS32一样快了。到了5.0版,DirectX已经相当完善,实现了该技术最初的承诺。对此我们将在第5章“DirectX基础和令人生畏的COM”涉及DirectX时再作详细介绍。现在要意识到:Win32和DirectX的组合是PC机上开发游戏的惟一选择。现在回头看历史。
Windows 98
1998年中期,Windows 98推出了。这至多是技术革命中的一步,而不像Windows 95那样是一个完全革命性的产品,但毫无疑问它也占有很重要的地位。Windows 98就像一辆由旧车改装而成的跑车——外观时髦,速度飞快,好得无以伦比。它是全32位的,能够支持你想到的任何事情,并具有无限扩充的能力。它很好地集成了DirectX、3D图形、网络以及Internet。
Windows 98和Windows 95相比也非常稳定。诚然,Windows 98仍然会死机,但是请相信我,比起Windows 95来已经少了许多。而且,Windows 98支持即插即用,并且支持得很好——是时候了!
Windows ME
在1999年下半年到2000年上半年间,Windows ME(或千年版,Millennium)发布了。很难解释ME,基本上还是98的内核,但更紧密地集成了多媒体和网络支持。ME是定位在消费者市场,而不是技术或商用市场。就功能性而言,它和Windows 98没什么两样。而且,由于ME较高的集成度,某些应用程序在上面运行会遇到麻烦。一些旧的硬件完全不被支持,ME只对配置较新的电脑来说是好的选择。比如你可以用一台1995年装配的电脑来很好地运行98,但是用它来跑ME肯定不行。话说回来,对于配置较新的电脑来说,用来游戏,这还是一个较可靠、稳定的操作系统。
Windows XP
在本书写作的时候,微软刚刚发布了新的操作系统Windows XP。我真的很喜欢用它。Windows XP可以说是我见过的最嗲的操作系统。它具有98或ME的外观,同时又有Windows 2000或NT的稳定和可靠性。XP是面向消费者的操作系统迈出的一大步。但是,也带来了不好的地方。XP是完全的32位Windows兼容操作系统。这些年来微软不停地在说服硬件软件开发商不可以自行其是,一定要遵守规范。这下好了,付出代价的时候到了,XP不支持很多违反了游戏规则的软件。从另一方面这也是好事,从长远来讲,所有的软件公司将会重新编译他们的程序,清理那些不良和硬件相关的代码。这些不良代码就是造成98和98都那么不稳定的罪魁祸首。因而,XP就像是一次涅磐——我们有最酷最时髦的操作系统,想用的话,一定要守规矩。
无论怎样,为了不至于成为兼容性讨论的灾难范本,XP为用户提供了两个帮助他们运行他们想要的软件的工具。第一,XP操作系统不断地通过微软更新来更新自己,有许多公司不停地在解决各种讨厌的软件问题。第二,XP有一个“兼容性”模式,通过在这个模式里运行,你可以运行原本与XP并不兼容的软件;简言之,只需屏蔽对错误的检测,软件就能运行。? 除了这些问题,我建议读者不要犹豫,尽快升级到Windows XP。
Windows NT/2000
现在我们来讨论一下Windows NT。在本书编写期间,Windows NT正在推出5.0版本,而且已经正式地被命名为Windows 2000。以我个人估计,它最终将取代Windows 9X成为每个人的操作系统选择。2000要比Windows 9X严谨得多;而且绝大多数游戏程序员都在NT上开发将在Windows 9X/ME/XP上运行的游戏。Windows 2000最酷的是它完全支持即插即用和Win32/DirectX,因此使用DirectX为Windows 9X编写的应用程序可以在Windows 2000上运行。这可是个好消息,因为从历史上看,是编写PC游戏的开发人员占有最大的市场份额。
那么最低标准是什么呢?如果你使用DirectX(或其他工具)编写了一个Win32应用程序,它完全可以在Windows 95、98、ME、XP和2000或更高版本上运行。这可是件好事情。因此你在本书中所学到的任何东西可以轻松应用到多种操作系统上。对了,甚至还包括Windows CE 3.0/Pocket PC 2002系统,因为它们也支持DirectX和Win32的一个子集。
Windows基本构架: Win9X/NT
和DOS不同,Windows是一个多任务的操作系统,允许许多应用程序和/或小程序同时运行,可以最大限度地发挥硬件的性能。这表明Windows是一个共享的环境——一个应用程序不能独占整个系统。尽管Windows 95、98、ME、XP和2000/NT它们都很相似,但仍然存在许多技术上的差异。但是就我们所关心的,我们可以进行一般性的讨论。这里所参照的Windows机器一般是指Win9X/NT或Windows环境。让我们开始吧!
多任务(Multitasking)和多线程(Multithreading)
如我所说,Windows允许不同的应用程序以轮询(Round-robin)的方式同时执行,每一个应用程序都占用一段很短的时间片来运行,而后就轮到下一个应用程序运行。如图2-1所示,CPU由几个不同的应用程序以循环的方式共享。而负责判断出下一个运行的应用程序、和给每个应用程序分配运行时间量是调度程序(Scheduler)的工作。
图2-1:在单处理器上进行多处理
调度程序可以非常简单——每个应用程序分配固定的运行时间,也可以非常复杂——将应用程序设定为不同的优先级和抢先性或低优先级的事件。就Win9X/NT而言,调度程序采用基于优先级的抢先占用方式。这就意味着一些应用程序要比其他的应用程序占用处理器更多的时间,但是如果一个应用程序需要CPU处理的话,在另一任务运行的同时,当前的任务可以被阻止或抢先占用。
但是不要太担心这些,除非你正在编写OS(操作系统)或实时代码——那样的话调度的细节事关重大。大多数情况下,Windows将执行和调度你的应用程序,无需你参与。
深入接触Windows,我们可以看到,它不仅是多任务的,而且还是多线程的。这意味着程序由许多较为简单的多个执行线程(Threads of Execution)构成。这些线程被视为具有较重的权值的进程——如程序一样,从而被调度。实际上,在同一时刻,你的计算机上有30~50个线程正同时运行,并执行着不同的任务。所以事实上你可能运行一个程序,但这个程序由一个或多个执行线程构成。
Windows实际的多线程示意图如图2-2所示,从图中可以看到,每一个程序实际上都是由一个主线程和几个工作线程构成。
图2-2:A more realistic multithreaded view of Windows.
获取线程的信息
下面让我们来看一下你的计算机现在正在运行多少个线程。在Windows机器上,同时按Ctrl+Alt+Delete键,弹出显示正在运行的任务(过程)的活动程序任务管理器。这和我们所希望的不同,但也很接近。我们希望的是一个显示正在执行的实际线程数的工具或程序。许多共享软件和商用软件工具都能做到这一点,但是Windows内嵌了这几个工具。
在安装Windows的目录(一般是Windows/)下,可以发现一个名字为SYSMON.EXE(Windows 95/98)或PREFMON.EXE(Windows NT)的可执行程序。图2-3描述了在我的Windows 98机器上运行的SYSMON.EXE程序。图中除了正在运行的线程外还有大量的信息,如内存使用和处理器装载等。实际上在进行程序开发时,我喜欢使SYSMON.EXE运行,由此可以了解正在进行什么以及系统如何加载程序。
图2-3:运行中的SYSM
你可能想知道能否对线程的创建进行控制,答案是能够!!!实际上这是Windows游戏编程最令人激动的事情之一——就像我们所希望的那样除了游戏主进程外,还能够执行其他的任务,我们也能够创建像其他任务一样多的线程。
注 意
在Windows 98/NT环境下,实际上还有一种叫纤程(Fiber)的新型执行对象,它比线程还简单(明白吗?线程是由纤程构成的)
这和DOS游戏程序的编写有很大不同。DOS是单线程操作系统,也就是说一旦你的程序开始运行,就只能运行该程序(不时出现的中断处理程序除外)。因此,如果想使用任何一种多任务或多线程,就必须自己来模拟(参阅《Sams Teach Yourself Game Programming in 21 Days》中关于一个完整的基于DOS的多任务内核的介绍)。这也正是游戏程序员在这么多年中所作的事。的确,模拟多任务和多线程远远不能和拥有一个完整的支持多任务和多线程的操作系统相提并论,但是对于单个游戏来讲,它足可以良好地工作。
在我们接触到真正的Windows编程和那些工作代码之前,我想提及一个细节。你可能在想,Windows真是一个神奇的操作系统,因为它允许多个任务和程序立即执行。请记住,实际上并不是这样的。如果只有一个处理器的话,那么一次也只能执行一个执行流、线程、程序或你所调用的任何对象。Windows相互之间的切换太快了,以至于看上去就像几个程序在同时运行一样。另一方面,如果有几个处理器的话,可以同时运行多个程序。例如,我有一个双CPU的Pentium II计算机,有两个400MHz的Pentium II处理器在运行Windows NT 5.0。使用这种配置,可以同时执行两个指令流。
我希望在不远的将来,个人计算机的新型微处理器结构能够允许多个线程或纤程同时执行,将这样一个目标作为处理器设计的一部分。例如,Pentium具有两个执行单元——U管和V管。因此它能够同时执行两个指令。但是,这两个指令都是来自同一个线程。类似的是Pentium II、II、IV能够同时执行多个简单的指令,但它们也需来自同一个线程。
事件模型
Windows是个多任务/多线程的操作系统,并且还是一个事件驱动的操作系统。和DOS程序不同的是,Windows程序都是等着用户去使用,并由此而触发一个事件,然后Windows对该事件发生响应,进行动作。请看图2-4所示的示意图,图中描述了大量的应用程序窗口,每个程序都向Windows发送待处理的事件和消息。Windows对其中的一些进行处理,大部分的消息和事件被传递给应用程序来处理。
图2-4:Windows event handling.
这样做的好处是你不必去关心其他的正在运行的应用程序,Windows会为你处理它们。你所要关心的就是你自己的应用程序和窗口中信息的处理。这在Windows 3.0/3.1中是根本不可能的。Windows的那些版本并不是真正的多任务操作系统,每一个应用程序都要产生下一个程序。也就是说,在这些版本的Windows下运行的应用程序感觉相当粗糙、缓慢。如果有其他应用程序干扰系统的话,这个正在“温顺地”运行的程序将停止工作。但这种情况在Windows 9X/NT下就不会出现。操作系统将会在适当的时间终止你的应用程序——当然,运行速度非常快,你根本就不会注意到。
到现在为止,读者已了解了所有有关操作系统的概念。幸运的是,有了Windows这个目前最好的编写游戏的操作系统,你根本就不必担心程序调度——你所要考虑的就是游戏代码和如何最大限度地发挥计算机的性能。
在本章后面内容中,我们要接触一些实际的编程工作,便于读者了解Windows编程有多容易。但是(永远都有但是)在进行实际编程之前,我们应当了解一些Microsoft程序员喜欢使用的约定。这样你就不会被那些古怪的函数和变量名弄得不知所措。
按照微软风格编程:匈牙利符号表示法
如果你正在运作一个像Microsoft一样的公司,有几千个程序员在干不同的项目,在某种程度上应当提出一个编写代码的标准方式。否则,结果将是一片混乱。因此一个名字叫Charles Simonyi的人负责创建了一套编写Microsoft代码的规范。这个规范一直以来都被用作编写代码的基本指导说明书。所有Microsoft的API、界面、技术文件等等都采用这些规范。
这个规范通常被称为匈牙利符号表示法,可能是因为为了创立这个规范他经常加班——弄得饥肠辘辘的原因吧(英文中饥饿(Hungry)和匈牙利(Hungary)谐音),也可能是因为他是匈牙利人。虽然不知道名字的由来,关键在于你还是必须了解这个规范,以便于你能够阅读Microsoft代码。
匈牙利符号表示法包括许多与下列命名有关的约定:
? 变量
? 函数
? 类型和常量
? 类
? 参数
表2-1给出了匈牙利符号表示法使用的前缀代码。这些代码在大多数情况下一半用于前缀变量名,其他约定根据名称确定。其他解释可以参考本表。
表2-1:匈牙利符号表示法使用的前缀代码
前缀 数据类型(基础类型)
c Char字符
by BYTE字节(无符号字符)
n Short短整数和整数(表示一个数)
i int整数
x, y Short短整数(通常用于x坐标和y坐标)
cx, cy short短整数(通常用于表示x和y的长度;c表示计数)
b BOOL(整数)
w UINT(无符号整数)和WORD(无符号字)
l LONG(长整数)
dw DWORD(无符号长整数)
fn 函数指针
s 字符串
sz, str 以一个字节的0(空值)终止的字符串
lp 32位指针
h 编号(常用于表示Windows对象)
msg 消息
变量的命名
变量的命名应用匈牙利符号表示法,变量可用表2-1中的前缀代码来表示。另外,当一个变量是由一个或几个子名构成时,每一个子名都要以大写字母开头。下面是几个例子:
char *szFileName; // a null terminated string
int *lpiData; // a 32-bit pointer to an int
BOOL bSemaphore; // a boolean value
WORD dwMaxCount; // a 32-bit unsigned WORD
据我所知没有规定函数的局部变量的命名规则,但是有一条全局变量的命名规则:
int g_iXPos; // a global x-position
int g_iTimer; // a global timer
char *g_szString; // a global NULL terminated string
总的来说,全局变量以g_或者有时就只用g开头。
函数的命名
函数和变量命名规则相同,但是没有前缀。换句话说,只需子名的第一个字母要大写。下面是几个例子:
int PlotPixel(int ix, int iy, int ic);
void *MemScan(char *szString);
而且,在函数名中使用下划线是非法的。例如,下面的函数名表示是无效的匈牙利符号表示法:
int Get_Pixel(int ix, int iy);
类型和常量的命名
所有的类型和常量都是大写字母,但名字中可以允许使用下划线。例如:
const LONG NUM_SECTORS = 100; // a C++ style constant
#define MAX_CELLS 64 // a C style constant
#define POWERUNIT 100 // a C style constant
typedef unsigned char UCHAR; // a user defined type
这儿并没有什么不同的地方——非常标准的定义。尽管大多数Microsoft程序员不使用下划线,但我还是喜欢用,因为这样能使名字更具有可读性。
提 示
在C++中,关键字const不止一个意思。在前面的代码行中,它用来创建一个常数变量。这和#define相似,但是它增加了类型信息这个特性。const不仅仅像#define一样是一个简单的预处理文本替换,而且更像是一个变量。它允许编译器进行类型检查和转型。
类的命名
类命名的约定可能要麻烦一点。但我也看到有很多人在使用这个约定,并独立地进行补充。不管怎样说,所有C++的类必须以大写C为前缀,类名字的每一个子名的第一个字母都必须大写。下面是几个例子:
class CVector
{
public:
CVector() {ix=iy=iz=imagnitude = 0;}
CVector(int x, int y, int z) {ix=x; iy=y; iz=z;}
.
.
private:
int ix,iy,iz; // the position of the vector
int imagnitude; // the magnitude of the vector
};
参数的命名
函数的参数命名和标准变量命名的约定相同。但也不总是如此。例如下面例子给出了一个函数定义:
UCHAR GetPixel(int x, int y);
这种情况下,更准确的匈牙利函数原型是:
UCHAR GetPixel(int ix, int iy);
但这两种写法我都见过。
其实,你甚至可能都看不到这些变量名,而仅仅看到类型,如下所示:
UCHAR GetPixel(int, int);
当然,这仅仅是原型使用的,真正的函数声明必须带有可绑定的变量名,这一点你已经掌握了。
注 意
学会阅读匈牙利符号表示法并不代表你必须总是使用它!实际上,我进行编程工作已经有20多年了,我也不准备为谁改变我的编程风格。因此,本书中的代码将在使用Win32 API函数的场合使用类匈牙利符号表示法的编码风格,而在其他位置将使用我自己的风格。必须注意的是,我使用的变量名的第一个字母没有大写,并且我还使用下划线。
世界上最简单的Windows程序
现在读者已经对Windows操作系统及其特性和基本设计问题有了一般的了解,那就让我们从第一个Windows程序开始真正的Windows编程吧。
以每一种新语言或所学的操作系统来编写一个输出“Hello World”文字的程序是一个习惯做法,让我们也来试试。程序清单2-1是标准的基于DOS的“Hello World”程序。
程序清单2-1:基于DOS的“Hello World”程序
// DEMO2_1.CPP - standard version
#include
// main entry point for all standard DOS/console programs
void main(void)
{
printf("/nTHERE CAN BE ONLY ONE!!!/n");
} // end main
现在让我们看一看如何在Windows下完成同样功能。
提 示
顺便说一句,如果想编译DEMO2_1.CPP,应当用VC++或Borland编译器实际创建一个控制台应用程序(CONSOLE APPLICATION)。这是类DOS的应用程序,只是它是32位的,它仅以文本模式运行,但对于检验一下想法和算法是很有用的。注意在编译器中应当将目标.EXE设为控制台应用程序,而非Win32 .EXE!
要编译本程序,请执行以下步骤:
1. 创建新的控制台应用程序.EXE工程,并包含光盘上T3DCHAP02/目录中的DEMO2_1.C

5601

被折叠的 条评论
为什么被折叠?



