窗口ignore_user_abort

ignore_user_abort  时间:2021-04-05  阅读:()
2Windows编程模型Windows编程就像去见牙科医生:明知道对你有好处,但就是没有人乐意去.
是不是这样在本章中,我将要使用"禅"的方法——或者换句话说,就是深入浅出地向你介绍Windows编程基础.
我可不能保证在阅读了本章后你就会"去见牙科医生",但是我敢保证你会比以往更喜欢Windows编程.
下面是本章的内容:QWindows的历史QWindows的基本风格QWindows的类Q创建WindowsQWindows事件句柄Q事件驱动编程和事件循环Q打开多个窗口Windows的历史读者可能会因为我要解放你的思想而感到十分恐惧(特别是钟情于DOS的顽固分子).
让我们迅速浏览一下Windows的发展历程以及与游戏发展的关系,好吗早期的Windows版本Windows的发展始于Windows1.
0版本.
这是Microsoft公司在商业视窗操作系统的第X第2章Windows编程模型X41一次尝试,当然是一个非常失败的产品.
Windows1.
0完全建立在DOS基础上(这就是一个错误),不能执行多任务,运行速度很慢,看上去也差劲.
它的外观可能是其失败的最重要原因.
除了讽刺以外,问题还在于Windows1.
0与那个时代的80286计算机(或更差的8086)所能提供的相比需要更高的硬件、图像和声音性能.
然而,Microsoft稳步前进,很快就推出了Windows2.
0.
我记得获得Windows2.
0的测试版时我正在软件出版公司工作.
在会议室中,挤满了公司的行政官员和董事长(像往常一样,他正拿着一杯鸡尾酒).
我们运行Windows2.
0测试演示版,装载了多个应用程序,看上去似乎还在工作.
但是,那时IBM推出了PM.
PM看上去要好得多,它是建立在比Windows2.
0先进得多的操作系统OS/2的基础上的,而Windows2.
0依然是建立在DOS基础上的视窗管理器.
那天会议室中的结论是"不错,但还不是一个可行的操作系统,如果我们仍然留恋在DOS上,那我还能有鸡尾酒喝吗"Windows3.
x1990年,终于发生了翻天覆地的变化,因为Windows3.
0出世了,而且其表现的确非常出色!
尽管它仍然赶不上MacOS的标准,但是谁还在意呢(真正的程序员都憎恨Mac).
软件开发人员终于可以在PC机上创建迷人的应用程序了,而商用应用程序也开始脱离DOS.
这成了PC机的转折点,终于将Mac完全排除在商用应用程序之外了,而后也将其挤出台式机出版业.
(那时,Apple公司每5分钟就推出一种新硬件).
尽管Windows3.
0工作良好,却还是存在许多的问题、软件漏洞,但从技术上说它已是Windows2.
0之后的巨大突破,有问题也是在所难免.
为了解决这些问题,Microsoft推出了Windows3.
1,开始公关部和市场部打算称之为Windows4.
0,但是,Microsoft决定只简单地称之为Windows3.
1,因为它还不足以称之为升级的换代版本.
它还没有做到市场部广告宣传的那样棒.
Windows3.
1非常可靠.
它带有多媒体扩展以提供音频和视频支持,而且它还是一个出色的、全面的操作系统,用户能够以统一的方式来工作.
另外,还存在一些其他的版本,如可以支持网络的Windows3.
11(适用于工作组的Windows).
惟一的问题是Windows3.
1仍然是一个DOS应用程序,运行于DOS扩展器上.
Windows95另一方面,游戏编程行业还在唱"DOS永存!
"的赞歌,而我则已经开始热衷于使用Windows3.
1.
但是,1995年世界开始冷却——Windows95终于推出.
它是一个真正32位的、多任务、多线程的操作系统.
诚然,其中还残存一些16位代码,但在极大程度上,Windows95是PC机的终极开发和发布平台.
(当然,WindowsNT3.
0也同时推出,但是NT对于大多数用户来讲还是不可用的,X游戏编程大师X42因此这里也就不再赘述).
当Windows95推出后,我才真正开始喜欢Windows编程.
我一直痛恨使用Windows1.
0、2.
0、3.
0和3.
1来编程,尽管随着每一种版本的推出,这种憎恨都越来越少.
当Windows95出现时,它彻底改变了我的思想,如同其他被征服的人的感觉一样——它看上去非常酷!
那正是我所需要的.
游戏编程行业中最重要的事情是游戏表现如何,游戏的画面如何.
同时还要尽可能减轻审阅人的工作.
因此几乎是一夜间,Windows95就改变了整个计算机行业.
的确,目前还有一些公司仍然在使用Windows3.
1(你能相信吗),但是Windows95使得基于Intel的PC成为除游戏之外的所有应用程序的选择.
不错,尽管游戏程序员知道DOS退出游戏编程行业只是一个时间的问题了,但是DOS还是它们的核心.
1996年,Microsoft公司发布了GameSDK(游戏软件开发工具包),这基本上就是DirectX的第一个版本.
这种技术仅能在Windows95环境下工作,但是它的确太慢了,甚至竞争不过DOS游戏(如DOOM和DukeNukem等).
游戏开发人员继续使用DOS32来开发游戏,但是他们知道DirectX具有足够快的速度,从而能使游戏能够流畅地运行在PC机上已为时不远.
到了3.
0版,DirectX的速度在同样计算机上已经和DOS32一样快了.
到了5.
0版,DirectX已经相当完善,实现了该技术最初的承诺.
对此我们将在第五章"DirectX基础和令人生畏的COM"涉及DirectX时再作详细介绍.
现在要意识到:Win32/DirectX是PC机上开发游戏的惟一方式.
这是历史的选择.
Windows981998年中期,Windows98推出了.
这至多是一个改进的版本,而不像Windows95那样是一个换代的产品,但毫无疑问它也是占有很重要的地位.
Windows98像一辆旧车改装的高速汽车——实际上它是一头皮毛圆润光滑、脚力持久、飞奔跳动的驴.
它是全32位的,能够支持你想做的所有事情,并具有无限扩充的能力.
它很好地集成了DirectX、3D图形、网络以及Internet.
Windows98和Windows95相比也非常稳定.
当然Windows98仍然经常死机,但可以相信的是,这比Windows95少了许多.
对即插即用支持得很好,并且能够很好地运行——这只是个时间问题.
提示X第2章Windows编程模型X43WindowsNT现在我们来讨论一下WindowsNT.
在本书编写期间,WindowsNT正在推出5.
0版本.
我所能说的是,它最终将取代Windows9X成为每个人的操作系统选择.
NT要比Windows9X严谨得多;而且绝大多数游戏程序员都将在NT上开发游戏,这将使Windows9X退出历史舞台.
WindowsNT5.
0最酷的是它完全支持即插即用和Win32/DirectX,因此使用DirectX为Windows9X编写的应用程序可以在WindowsNT5.
0或更高版本上运行.
这可是个好消息,因为从历史上看,编写PC游戏的开发人员现在具有最大的市场潜力.
那么最低标准是什么呢如果你使用DirectX(或其他工具)编写了一个Win32应用程序,它完全可以在Windows95、98和NT5.
0或更高版本上运行.
这可是件好事情.
因此你在本书中所学到的任何东西至少可以应用到三种操作系统上,也可以运行于安装NT和DirectX的其他计算机上,如DEC的Alphas.
还有WindowsCE——DirectX和Win32衍生的运行于其他系统上的操作系统.
Windows的基本风格:Win9X/NT和DOS不同,Windows是一个多任务的操作系统,允许许多应用程序和更小的程序同时运行,可以最大限度的发挥硬件的性能.
这表明Windows是一个共享的环境——一个应用程序不可能独占整个系统.
尽管Windows95、98和NT很相似,但仍然存在许多技术上的差别.
但是就我们所涉及的,不可能去详细归纳.
这里所参照的Windows机器一般是指Win9X/NT或Windows环境.
让我们开始吧!
多任务和多线程如我所说,Windows允许不同的应用程序以轮流的方式同时执行,每一个应用程序都占用一段很短的时间段来运行,下一个应用程序轮换运行.
如图2.
1所示,CPU由几个不同的应用程序以轮流的方式共享.
判断出下一个运行的应用程序、分配给每个应用程序的时间量是调度程序的工作.
调度程序可以非常简单——每个应用程序分配固定的运行时间,也可以非常复杂——将应用程序设定为不同的优先级和抢先性或低优先级的事件.
就Win9X/NT而言,调度程序采用基于优先级的抢先占用方式.
这就意味着一些应用程序要比其他的应用程序占用处理器更多的时间,但是如果一个应用程序需要CPU处理的话,在另一任务运行的同时,当前的任务可以被锁定或抢先占用.
但是不要对此有太多担心,除非你正在编写OS(操作系统)或实时代码——其细节事X游戏编程大师X44关重大.
大多数情况下,Windows将执行和调度你的应用程序,无需你参与.
深入接触Windows,我们可以看到,它不仅是多任务的,而且还是多线程的.
这意味着程序由许多更简单的多个执行线程构成.
这些线程(像更重要的进程)如程序一样被调度.
实际上,在你的计算机上可同时运行30~50个线程,执行不同的任务.
所以事实上你可能只运行一个程序,但这个程序由一个或多个执行线程构成.
Windows实际的多线程示意图如图2.
2所示,从图中可以看到,每一个程序实际上都是由一个主线程和几个工作线程构成.
图2.
1一个处理器执行多个程序的操作图2.
2Windows的更实际的多流程示意图获取线程的信息下面让我们来看一下你的计算机现在正在运行多少个线程.
在Windows机器上,同时按Ctrl+Alt+Delete键,弹出显示正在运行的任务(过程)的当前程序任务管理器.
这和我们所希望的不同,但也很接近.
我们希望的是一个显示正在执行的实际线程数的工具或程序.
进程0游戏程序进程1打印机假脱机系统绘制程序进程3字处理器进程2Intel内部CPU进程0线程0线程1线程2进程1线程0线程1进程n线程0线程1线程2线程3每个线程执行少量时间X第2章Windows编程模型X45许多共享软件和商用软件工具都能做到这一点,但是Windows内嵌了这几个工具.
在安装Windows的目录(一般是Windows\)下,可以发现一个名字为SYSMON.
EXE(Windows95/98)或PREFMON.
EXE(WindowsNT)的可执行程序.
图2.
3描述了在我的Windows98机器上运行的SYSMON.
EXE程序.
图中除了正在运行的线程外还有大量的信息,如:内存使用和处理器装载等.
实际上在进行程序开发时,我喜欢使SYSMON.
EXE运行,由此可以了解正在进行什么以及系统如何加载程序.
图2.
3运行的SYSMON.
EXE你可能想知道能否对线程的创建进行控制,答案是能够!
!
!
实际上这是Windows游戏编程最令人激动的事情之一——就像我们所希望的那样除了游戏主进程外,还能够执行其他的任务,我们也能够创建像其他任务一样多的线程.
在Windows98/NT环境下,实际上还有一种叫fiber的新型执行对象,它比线程还简单(明白吗线程是由fiber构成的).
这和DOS游戏程序的编写有很大不同.
DOS是单线程操作系统,也就是说一旦你的程序开始运行,就只能运行该程序(不时出现的中断管理除外).
因此,如果想使用任何一种多任务或多线程,就必须自己来模拟(参阅《SamsTeachYourselfGameProgrammingin21Days》中关于一个完整的基于DOS的多任务核心部分).
这也正是游戏程序员在这么多年中所作的事.
的确,模拟多任务和多线程远远不能和拥有一个完整的支持多任务和多线程的操作系统相提并论,但是对于单个游戏来讲,它足可以良好地工作.
在我们接触到真正的Windows编程和那些工作代码之前,我想提及一个细节.
你可能在想,Windows真是一个神奇的操作系统,因为它允许多个任务和程序立即执行.
请记住,实际上并不是这样的.
如果只有一个处理器的话,那么一次也只能执行一个执行流、线程、程序或你所调用的任何对象.
Windows相互之间的切换太快了,以至于看上去就像几个程序注意X游戏编程大师X46在同时运行一样.
另一方面,如果有几个处理器的话,可以同时运行多个程序.
例如,我有一个双CPU的PentiumII计算机,有两个400MHz的PentiumII处理器在运行WindowsNT5.
0.
使用这种配置,可以同时执行两个指令流.
我希望在不远的将来,个人计算机的新型微处理器结构能够允许多个线程或fiber同时执行,将这样一个目标作为处理器设计的一部分.
例如,Pentium具有两个执行单元——U管和V管.
因此它能够立即执行两个指令.
但是,这两个指令都是来自同一个线程.
类似的是PentiumII能够立即执行5个简单的指令,但也是来自同一个线程.
事件模型Windows是个多任务/多线程的操作系统,并且还是一个事件驱动的操作系统.
和DOS程序不同的是,Windows程序都是等着用户去使用,由此而触发一个事件,然后Windows对该事件发生响应,进行动作.
请看图2.
4所示的示意图,图中描述了大量的应用程序窗口,每个程序都向Windows发送待处理的事件和消息.
Windows对其中的一些进行处理,大部分的消息和事件被传递给应用程序来处理.
图2.
4Windows事件管理程序这样做的好处是你不必去关心其他的正在运行的应用程序,Windows会为你处理它们.
你所要关心的就是你自己的应用程序和窗口中信息的处理.
这在Windows3.
0/3.
1中是根本不可能的.
Windows的那些版本并不是真正的多任务操作系统,每一个应用程序都要产生下一个程序.
也就是说,在这些版本的Windows下运行的应用程序感觉相当粗糙、缓慢.
如果有其他应用程序干扰系统的话,这个正在"温顺地"运行的程序将停止工作.
但这种情况窗口1系统事件队列局部事件队列窗口3窗口2消息被传送至每个窗口应用程序局部事件队列局部事件队列X第2章Windows编程模型X47在Windows9X/NT下就不会出现.
操作系统将会在适当的时间终止你的应用程序——当然,运行速度非常快,你根本就不会注意到.
到现在为止,读者已了解了所有有关操作系统的概念.
幸运的是,有了Windows这个目前最好的编写游戏的操作系统,读者根本就不必担心程序调度——你所要考虑的就是游戏代码和如何最大限度地发挥计算机的性能.
在本章后面内容中,我们要接触一些实际的编程工作,便于读者了解Windows编程有多容易.
但是(永远都有但是)在进行实际编程之前,我们应当了解一些Microsoft程序员喜欢使用的约定.
这样你就不会被那些古怪的函数和变量名弄得不知所措.
按照Microsoft方式编程:匈牙利符号表示法如果你正在运作一个像Microsoft一样的公司,有几千个程序员在干不同的项目,在某一点上就应当提出一个编写代码的标准方式.
否则,结果将是一片混乱.
因此一个名字叫CharlesSimonyi的人被委托创立了一套编写Microsoft代码的规范.
这个规范已经用作编写代码的基本指导说明书.
所有Microsoft的API、界面、技术文件等等都采用这些规范.
这个规范通常被称为匈牙利符号表示法,可能是因为创立这个规范工作花了很长时间,弄得他饥肠辘辘的原因吧(英文中饥饿和匈牙利谐音),或者可能他是匈牙利人.
对此我们根本就不知道,关键是你必须了解这个规范,以便于你能够阅读Microsoft代码.
匈牙利符号表示法包括许多与下列命名有关的约定:·变量·函数·类型和常量·类·参数表2.
1给出了匈牙利符号表示法使用的前缀代码.
这些代码在大多数情况下一半用于前缀变量名,其他约定根据名称确定.
其他解释可以参考本表.
表2.
1匈牙利符号表示法的前缀代码指导说明书前缀数据类型(基本类型)c字符by字节(无符号字符)n短整数和整数(表示一个数)i整数x,y短整数(通常用于x坐标和y坐标)X游戏编程大师X48续表前缀数据类型(基本类型)cx,cy短整数(通常用于表示x和y的长度;c表示计数)b布尔型(整数)wUINT(无符号整数)和WORD(无符号字)lLONG(长整数)dwDWORD(无符号长整数)fn函数指针s串sz,str以0字节终止的字符串lp32位的长整数指针h编号(常用于表示Windows对象)msg消息变量的命名应用匈牙利符号表示法,变量可用表2.
1中的前缀代码来表示.
另外,当一个变量是由一个或几个子名构成时,每一个子名都要以大写字母开头.
下面是几个例子:char*szFileName;//anullaterminatedstringint*lpiDate;//a32-bitpointertoanintBOOLbSemaphore;//abooleanvalueWORDdwMaxCount;//a32-bitunsignedWORD尽管我了解一个函数的局部变量没有说明,但是也有个别表示全局变量:intg_iXPos;//aglobalx-positionintg_iTimer;//aglobaly-positionchar*g_szString;//aglobalNULLterminatedstring总的来说,变量以g_开头,或者有时就只用g.
函数的命名函数和变量命名方式相同,但是没有前缀.
换句话说,子名的第一个字母要大写.
下面是几个例子:intPlotPixel(intix,intiy,intic);void*MemScan(char*szString);而且,下划线是非法的.
例如,下面的函数名表示是无效的匈牙利符号表示法:X第2章Windows编程模型X49intGet_Pixel(intix,intiy);类型和常量的命名所有的类型和常量都是大写字母,但名字中可以允许使用下划线.
例如:constLONGNUM_SECTORS=100;//aC++styleconstant#defineMAX_CELLS64;//aCstyleconstant#definePOWERUNIT100;//aCstyleconstanttypedefunsignedcharUCHAR;//auserdefinedtype这儿并没有什么不同的地方——非常标准的定义.
尽管大多数Microsoft程序员不使用下划线,但我还是喜欢用,因为这样能使名字更具有可读性.
在C++中,关键字const不止一个意思.
在前面的代码行中,它用来创建一个常数变量.
这和#define相似,但是它增加了类型信息这个特性.
Const不仅仅像#define一样是一个简单的预处理文本替换,而且更像是一个变量.
它允许编译器进行类型检查和替换.
类的命名类命名的约定可能要麻烦一点.
但我也看到有很多人在使用这个约定,并独立地进行补充.
不管怎样说,所有C++的类必须以大写C为前缀,类名字的每一个子名的第一个字母都必须大写.
下面是几个例子:classCVector{publicCVector();{ix=iy=iz=imagnitude=0;}CVector(intx,inty,intz){ix=x;iy=y;iz=z;}.
.
private:intix,iy,iz;//thepositionofthevectorintimagnitude;//themagnitudeofthevector};参数的命名函数的参数命名和标准变量命名的约定相同.
但也不总是如此.
例如下面例子给出了一个函数定义:C++X游戏编程大师X50UCHARGetPixel(intx,inty);这种情况下,更准确的匈牙利函数原型是:UCHARGetPixel(intix,intiy);但我认为这并没有什么两样.
最后,你甚至可能都看不到这些变量名,而仅仅看到类型,如下所示:UCHARGetPixel(int,int);当然,这仅仅是原型使用的,真正的函数声明必须带有可赋值的变量名,这一点你已经掌握了.
仅仅会读匈牙利符号表示法并不代表你能使用它.
实际上,我进行编程工作已经有20多年了,我也不准备为谁改变我的编程风格.
因此,本书中的代码使用类匈牙利符号表示法的编码风格,这是Win32API造成的,在其他位置将使用我自己的风格.
必须注意的是,我使用的变量名的第一个字母没有大写,并且我还使用下划线.
世界上最简单的Windows程序现在读者已经对Windows操作系统及其性能和基本设计问题有了一般的了解,那就让我们从第一个Windows程序开始真正的Windows编程吧.
以每一种新语言或所学的操作系统来编写一个"世界你好"的程序是一个惯例,让我们也来试试.
清单2.
1是标准的基于DOS的"世界你好"程序.
程序清单2.
1基于DOS的"世界你好"程序//DEMO2_1.
CPP-standardversion#include//mainentrypointforallstandardDOS/consoleprogramsvoidmain(void){printf("\nTHERECANBEONLYONE!
!
!
\n");}//endmain现在让我们看一看用Windows如何编写它.
注意X第2章Windows编程模型X51顺便说一句,如果读者想编译DEMO2_1.
CPP的话,就应当用VC++或Borland编译器实际创建一个调用内容的控制应用程序.
这是一个类DOS的应用程序,只是它是32位的,它仅以文本模式运行,但对于检验一个想法和算法是很有用的.
总是从WinMain()开始如前面所述,所有的Windows程序都以WinMain()开始,这和简单直观的DOS程序都以Main()开始一样.
WinMain()中的内容取决于你.
如果你愿意的话,可以创建一个窗口、开始处理事件并在屏幕上画一些东西.
另一方面,你可以调用几百个(或者是几千个)Win32API函数中的一个.
这正是我们将要做的.
我只想在屏幕上的一个信息框中打印一点东西.
这正是一个Win32API函数MessageBox()的功能.
清单2.
2是一个完整的、可编译的Windows程序,该程序创建和显示了一个能够到处移动和关闭的信息框.
程序清单2.
2第一个Windows程序//DEMO2_2.
CPP-asimplemessagebox#defineWIN32_LEAN_AND_MEAN#include//themainwindowsheaders#include//alotofcoolmacros//mainentrypointforallwindowsprogramsintWINAPIWinMain(HINSTANCEhinstance,HINSTANCEhprevinstance,LPSTRlpcmdline,intncmdshow){//callmessageboxapiwithNULLforparentwindowhandleMessageBox(NULL,"THERECANBEONLYONE!
!
!
","MYFIRSTWINDOWSPROGRAM",MB_OK|MB_ICONEXCLAMATION);//exitprogramreturn(0);}//endWinMain要编译该程序,按照下面步骤:1.
创建新的Win32.
EXE项目并包含CD-ROM上T3DCHAP02\下的DEMO2_2.
CPP.
2.
编译和联接程序.
技巧X游戏编程大师X523.
运行!
(或在CD-ROM上直接运行预编译版本DEMO2_2.
EXE).
你可能会以为一个基本的Windows程序有几百行代码.
当你编译和运行程序时,可能会看到如图2.
5所示的内容.
图2.
5运行DEMO2_2.
EXE程序剖析现在已经有了一个完整的Windows程序,让我们一行一行地来分析程序的内容.
首先第一行程序是#defineWin32_LEAN_AND_MEAN这个应稍微解释一下.
创建Windows程序有两种方式——使用Microsoft基础类(MicrosoftFoundationClasses,MFC),或者使用软件开发工具包(SoftwareDevelopmentKit,SDK).
MFC完全基于C++和类,要比以前的游戏编程所需的工具复杂得多,功能和难度也要强大和复杂10倍.
而SDK是一个可管理程序包,可以在一到两周内学会(至少初步学会),并且使用了简单明了的C语言.
因此,我在本书中所使用的工具是SDK.
Win32_LEAN_AND_MEAN指示编译器(实际上是逻辑头文件)不包含无关的MFC操作.
现在我们又离题了,回来继续看程序.
之后,下面的头文件是:#include"windows.
h"#include"windowsx.
h"第一个引用"windows.
h"实际上包括所有的Windows头文件.
Windows有许多这样的头文件,这就有点像包含宏,可以节省许多手工包含显式头文件的时间.
第二个引用"windowsx.
h"是一个含有许多重要的宏和常量的头文件,该文件可以简化Windows编程.
下面就到了最重要的部分——所有Windows应用程序的主要入口位置WinMain():intWINAPIWinMain(HINSTANCEhinstance,HINSTANCEhprevinstance,LPSTRlpcmdline,intncmdshow);X第2章Windows编程模型X53首先,应当注意到奇怪的WINAPI声明符.
这相当于PASCAL函数声明符,它强制参数从左边向右边传递,而不是像默认的CDECL声明符那样参数从右到左转移.
但是,PASCAL调用约定声明已经过时了,WINAPI代替了该函数.
必须使用WinMain()的WINAPI声明符;否则,将向函数返回一个不正确的参数并终止开始程序.
测试参数下面让我们详细看一下每个参数:·hinstance——该参数是一个Windows为你的应用程序生成的实例句柄.
实例是一个用来跟踪资源的指针或数.
本例中,hinstance就像一个名字或地址一样,用来跟踪你的应用程序.
·hprevinstance——该参数已经不再使用了,但是在Windows的旧版本中,它跟踪应用程序以前的实例(换句话说,就是产生当前实例的应用程序实例).
难怪Microsoft要去除它,它就像一次长途跋涉——让我们为之头疼.
·lpcmdline——这是一个空终止字符串,和标准C/C++main(intargc,char**argv)函数中的命令行参数相似.
不同的是,它不是一个单独的像argc那样指出命令行的参数.
例如,如果你创建一个名字为TEST.
EXE的Windows应用程序,并且使用下面的参数运行:TEST.
EXEonelpcmdline将含有下面数据:lpcmdline="onetwothree"注意,.
EXE的名字本身并不是命令行的一部分.
·ncmdshow——最后一整型参数在运行过程中被传递给应用程序,带有如何打开主应用程序窗口的信息.
这样,用户便会拥有一点控制应用程序如何启动的能力.
当然,作为一个程序员,如果想忽略它也可以,而想使用它也行.
(你将参数传递给ShowWindow(),我们又超前了!
)表2.
2列出了ncmdshow最常用的参数值.
表2.
2ncmdshow的Windows代码值功能SW_SHOWNORMAL激活并显示一个窗口.
如果该窗口最小化或最大化的话,Windows将它恢复到原始尺寸和位置.
当第一次显示该窗口时,应用程序将指定该标志SW_SHOW激活一个窗口,并按当前尺寸和位置显示SW_HIDE隐藏一个窗口,并激活另外一个窗口X游戏编程大师X54续表值功能SW_MAXIMIZE将指定的窗口最大化SW_MINIMIZE将指定的窗口最小化SW_RESTORE激活并显示一个窗口.
如果该窗口最小化或最大化的话,Windows将它恢复到原始尺寸和位置.
当恢复为最小化窗口时,应用程序必须指定该标志SW_SHOWMAXIMIZED激活一个窗口,并以最大化窗口显示SW_SHOWMINIMIZED激活一个窗口,并以最小化窗口显示SW_SHOWMINNOACTIVE以最小化窗口方式显示一个窗口,激活的窗口依然保持激活的状态SW_SHOWNA以当前状态显示一个窗口,激活的窗口依然保持激活的状态SW_SHOWNOACTIVATE以上一次的窗口尺寸和位置来显示窗口,激活的窗口依然保持激活的状态如表2.
2所示,ncmdshow有许多设置(目前许多值都没有意义).
实际上,这些设置大部分都不在ncmdshow中传递.
可以应用另一个函数ShowWindow()来使用它们,该函数在一个窗口创建时就开始显示.
对此我们在本章后面将进行详细讨论.
我想说的一点是,Windows带有大量的你从未使用过的选项和标志等等,就像VCR编程选项一样——越多越好,任你使用.
Windows就是按照这种方式设计的.
这将使每个人都感到满意,这也意味着它包含了许多选项.
实际上,我们在99%时间内将会使用SW_SHOW、SW_SHOWNORMAL和SW_HIDE,但是你还要了解在1%的时间内会用到的其他选项.
选择一个信息框最后让我们讨论一下WinMain()中调用MessageBox()的实际机制.
MessageBox()是一个Win32API函数,它替我们做某些事,使我们不需自己去做.
该函数经常以不同的图标和一个或两个按钮来显示信息.
你看,简单的信息显示在Windows应用程序中非常普通,有了这样一个函数就节省了程序员半个多小时的时间,而不必每次使用都要编写它.
MessageBox()并没有什么多少功能,但是能够在屏幕上显示一个窗口,提出一个问题,并且等候用户的输入.
下面是MessageBox()的原型:intMessageBox(HWNDhwnd,//handleofownerwindowLPCTSTRlptext,//addressoftextinmessageboxLPCTSTRlpcaption,//addressoftitleofmessageboxUINTutype);//styleofmessagebox参数定义如下:hwnd——这是信息框连接窗口的句柄.
目前我们还不能谈及窗口句柄,因此只能认为它是信息框的父窗口.
在DEMO2_2.
CPP,我们将它设置为空值NULL,因此使用WindowsX第2章Windows编程模型X55桌面作为父窗口.
lptext——这是一个包含显示文本的空值终止字符串.
lpcaption——这是一个包含显示文本框标题的空值终止字符串.
utype——这大概是该簇参数中惟一令人激动的参数了,控制信息显示框的种类.
表2.
3列出了几种MessageBox()选项(有些删减).
表2.
3MessageBox()选项标志描述下列设置控制信息框的一般类型MB_OK信息框含有一个按钮:OK,这是默认值MB_OKCANCEL信息框含有两个按钮:OK和CancelMB_RETRYCANCEL信息框含有两个按钮:Retry和CancelMB_YESNO信息框含有两个按钮:Yes和NoMB_YESNOCANCEL信息框含有三个按钮:Yes、No和CancelMB_ABORTRETRYIGNORE信息框含有三个按钮:Yes、No和Cancel这一组控制在图标上添加一点"穷人的多媒体"MB_ICONEXCLAMATION信息框显示一个惊叹号图标MB_ICONINFORMATION信息框显示一个由圆圈中的小写字母I构成的图标MB_ICONQUESTION信息框显示一个问号图标MB_ICONSTOP信息框显示一个终止符图标该标志组控制默认时高亮的按钮MB_DEFBUTTONn其中n是一个指示默认按钮的数字(1~4),从左到右计数注意:还有其他的高级OS级标志,我们没有讨论.
如果希望了解更多细节的话,可以通过编译器Win32SDK的在线帮助来查阅.
可以同时使用表2.
3中的值进行逻辑或运算,来创建一个信息框.
一般情况下,只能从每一组中仅使用一个标志来进行或运算.
当然,和所有Win32API函数一样,MessageBox()函数返回一个值来通知编程者所发生的事件.
但在这个例子中谁关心这个呢通常情况下,如果信息框是yes/no提问之类的情况的话,就希望知道这个返回值.
表2.
4列出了可能的返回值.
表2.
4MessageBox()的返回值X游戏编程大师X56值按钮选择IDABORTAbortIDCANCELCancelIDIGNOREIgnoreIDNONoIDOKOKIDRETRYRetryIDYESYes最后,这个表已经毫无遗漏地列出了所有的返回值.
现在已经完成了对我们第一个Windows程序——单击的逐行分析.
现在希望你能轻松地对这个程序进行修改,并以不同的方式进行编译.
使用不同的编译器选项,例如优化.
然后尝试通过调试程序来运行该程序,看看你是否已经领会.
做完后,请回到此处.
如果希望听到声音的话,一个简单的技巧就是使用MessageBeep()函数,可以在Win32SDK中查阅,它和MessageBox()函数一样简单好用.
下面就是该函数原型:BOOLMessageBeep(UINTutype);//运行声音可以从表2.
5所示常数中得到不同的声音.
表2.
5MessageBeep()函数的声音标识符值声音MB_ICONASTERISK系统星号MB_ICONEXCLAMATION系统惊叹号MB_ICONHAND系统指针MB_ICONQUESTION系统问号MB_OK系统默认值0xFFFFFFFF使用计算机扬声器的标准嘟嘟声,令人讨厌!
注意:如果已经安装了MS-Plus主题曲的话,你应能得到有意思的结果.
看Win32API多酷啊!
可以有上百个函数使用.
它们虽然不是世界上最快的函数,但是对于一般的内部管理I/O和GUI来讲,它们已经很棒了.
让我们稍微花点时间总结一下我们目前所知的有关Windows编程方面的知识.
首先,Windows支持多任务/多线程,因此可以同时运行多个应用程序.
我们不必费心就可以做到这一点.
我们最关心的是Windows支持事件触发.
这就意味着我们必须处理事件(在这一点上目前我们还不知如何做)并且做出反应.
好,听上去不错.
最后所有Windows程序都以函数WinMain()开始,WinMain()函数中的参数要比标准DOSMain()多得多,但这些参数都属于逻辑和推理的领域.
掌握了上述的内容,就到了编写一个真正的Windows应用程序的时候了.
技巧X第2章Windows编程模型X57真实的Windows应用程序尽管本书的目标是编写在Windows环境下运行的3D游戏,但是你并不需要了解更多的Windows编程.
实际上,你所需要的就是一个基本的Windows程序,可以打开一个窗口、处理信息、调用主游戏循环等等.
了解了这些,本章中的目标是首先向你展示如何创建简单的Windows应用程序,同时为编写类似32位DOS环境的游戏外壳程序奠定基础.
一个Windows程序的关键就是打开一个窗口.
一个窗口就是一个显示文本和图形信息的工作区.
要创建一个完全实用的Windows程序,只要进行下列工作:1.
创建一个Windows类.
2.
创建一个事件句柄或WinProc.
3.
用Windows注册Windows类.
4.
用前面创建的Windows类创建一个窗口.
5.
创建一个能够从事件句柄获得或向事件句柄传递Windows信息的主事件循环.
让我们详细了解一下每一步的工作.
Windows类Windows实际上是一个面向对象的操作系统,因此Windows中大量的概念和程序都出自C++.
其中一个概念就是Windows类.
Windows中的每一个窗口、控件、列表框、对话框和小部件等等实际上都是一个窗口.
区别它们的就是定义它们的类.
一个Windows类就是Windows能够操作的一个窗口类型的描述.
有许多预定义的Windows类,如按钮、列表框、文件选择器等等.
你也可以自己任意创建你的Windows类.
实际上,你可以为自己编写的每一个应用程序创建至少一个Windows类.
否则你的程序将非常麻烦.
因此你应当在画一个窗口时,考虑一个Windows类来作为Windows的一个模板,以便于在其中处理信息.
控制Windows类信息的数据结构有两个:WNDCLASS和WNDCLASSEX.
WNDCLASS是比较古老的一个,可能不久将废弃,因此我们应当使用新的扩展版WNDCLASSEX.
二者结构非常相似,如果有兴趣的话,可以在Win32帮助中查阅WNDCLASS.
让我们看一下在Windows头文件中定义的WNDCLASSEX.
typedefstruct_WNDCLASSEX{UINTcbSize;//sizeofthisstructureUINTstyle;//styleflagsX游戏编程大师X58WNDPROClpfnWndProc;//functionpointertohandlerintcbClsExtra;//extraclassinfointcbWndExtra;//extrainstanceinfoHANDLEhInstance;//theinstanceoftheapplicationHICONhIcon;//themainiconHCURSORhCursor;//thecursorforthewindowHBRUSHhbrBackground;//thebackgroundbrushtopaintthewindowLPCTSTRlpszMenuName;//thenameofthemenutoattachLPCTSTRlpszClassName;//thenameoftheclassitselfHICONhIconSm;//thehandleofthesmallicon}WNDCLASSEX因此你所要做的就是创建一个这样的结构,然后填写所有的字段:WNDCLASSEXwinclass;//ablankwindowsclass第一个字段cbSize非常重要(但Petzold在《ProgramingWindows95》中忘记了这个内容),它是WNDCLASSEX结构本身的大小.
你可能要问,为什么应当知道该结构的大小这个问题问得好,原因是如果这个结构作为一个指针被传递的话,接收器首先检查第一个字段,以确定该数据块最低限度有多大.
这有点像提示和帮助信息,以便于其他函数在运行时不必计算该类的大小.
因此,我们应当这样做:winclass.
cbSize=sizeof(WNDCLASSEX);第二个字段包含描述该窗口一般属性的结构信息标志.
有许多这样的标志,因此我们不能全部列出它们.
只要能够使用它们创建任何类型的窗口就行了.
表2.
6列出了常用的标志.
读者可以任意对这些值进行逻辑"或"运算,来派生所希望的窗口类型.
表2.
6Windows类的类型标志标志说明CS_HREDRAW若移动或改变了窗口宽度,则刷新整个窗口CS_VREDRAW若移动或改变了窗口高度,则刷新整个窗口CS_OWNDC为该类中每个窗口分配一个单值的设备描述表(在本章后面详细描述)CS_DBLCLKS当用户双击鼠标时向窗口程序发送一个双击的信息,同时,光标位于属于该类的窗口中CS_PARENTDC在母窗口中设定一个子窗口的剪切区,以便于子窗口能够画在母窗口中CS_SAVEBITS在一个窗口中保存用户图像,以便于在该窗口被遮住、移动时不必每次刷新屏幕.
但是,这样会占用更多的内存,并且比人工同样操作要慢得多CS_NOCLOSE禁止系统菜单上的关闭命令注意:用黑体显示的部分为最常用的标志.
表2.
6包含了大量的标志,即使读者对此尚有疑问,也无关紧要.
现在,设定类型标识X第2章Windows编程模型X59符,描述如果窗口移动或改变尺寸就进行屏幕刷新,并可以获得一个静态的设备描述表以及处理双击事件的能力.
我们将在第三章"高级Windows编程"中详细讨论设备描述表,但基本说来,它被用作窗口中图像着色的数据结构.
因此,如果你要处理一个图像,就应为感兴趣的特定窗口申请一个设备描述表.
如果设定了一个Windows类,它就通过CS_OMNDC得到了一个设备描述表,如果你不想每次处理图像时都申请设备描述表,可以将它保存一段时间.
上面说的对你有帮助还是使你更糊涂Windows就是这样——你知道得越多,问题就越多.
好了!
下面说一下如何设定类型字段:winclass.
style=CS_VERDRAW|CS_HREDRAW|CS_OWNDC|CS_DBLCLICKS;WNDCLASSEX结构的下一个字段lpfnWndProc是一个指向事件句柄的函数指针.
基本上这里所设定的都是该类的回调函数.
回调函数在Windows编程中经常使用,工作原理如下:当有事件发生时,Windows通过调用一个你已经提供的回调函数来通知你,这省去你盲目查询的麻烦.
随后在回调函数中,再进行所需的操作.
这个过程就是基本的Windows事件循环和事件句柄的操作过程.
向Windows类申请一个回调函数(当然需要使用特定的原型).
当一个事件发生时,Windows按如图2.
6所示的的那样替你调用它.
关于该项内容我们将在下面部分进行更详细的介绍.
但是现在,读者只要将其设定到你将编写的事件函数中去:winclass.
lpfnWndProc=WinProc;//这是我们的函数图2.
6Windows事件句柄回调函数的操作流程函数指针有点像C++中的虚函数.
如果你对它们不熟悉的话,在这里讲一下.
假设有两个函数用以操作两个数:intAdd(intop1,intop2){return(op1+op2);}intSub(intop1,intop2){return(op1-op2);}应用程序运行循环所提供的事件句柄自动调用C++X游戏编程大师X60要想用同一调用来调用两个函数,可能用一个函数指针来实现.
如下://defineafunctionpointerthattakestwointandreturnsanintint(Math*)(int,int);然后可以如下配置函数指针:Math=Add;intresult=Math(1,2);//thisresllycallsAdd(1,2)//resultwillbe3Math=Sub;Intresult=Math(1,2);//thisreallycallsSub(1,2)//resultwillbe–1看,不错吧.
下面两个字段,cbClsExtra和cbWndExtra原是为指示Windows将附加的运行时间信息保存到Windows类某些单元中而设计的.
但是绝大多数人使用这些字段并简单地将其值设为0,如下所示:winclass.
cbClsExtra=0;//extraclassinfospacewinclass.
cbWndExtra=0;//extrawindowinfospace下一个是hInstance字段.
这是一个简单的、在启动时传递给WinMain()函数的句柄实例,因此只需简单地从WinMain()中复制即可:winclass.
hInstance=hinstance;//assigntheapplicationinstance剩下的字段和Windows类的图像方面有关,在讨论它们之前,先花一点时间回顾一下句柄.
在Windows程序和类型中将一再看到句柄:位图句柄、光标句柄、任意事情的句柄.
请记住,句柄只是一个基于内部Windows类型的标识符.
其实它们都是整数.
但是Microsoft可能改变这一点,因此安全使用Microsoft类型是个好主意.
总之,你将会看到越来越多的"[…]句柄",请记住,有前缀h的任何类型通常都是一个句柄.
好,回到原来的地方继续吧.
下一个字段是设定表示应用程序的图标的类型.
你完全可以装载一个你自己定制的图标,但现在你使用系统图标,需要为它设置一个句柄.
要为一个常用的系统图标检索一个句柄,可以使用LoadIcon()函数:winclass.
hIcon=LoadIcon(NULL,IDI_APPLICATION);这行代码装载一个标准的应用程序图标——虽然烦人,但是简单.
如果对IoadIcon()函数有兴趣的话,请看下面的它的原型,表2.
7给出了几个图标选项:HICONLoadIcon(HINSTANCEhInstance,//handleofapplicationinstanceLPCTSTRlpIconName);//icon-namestringoriconresourceidentifierC++X第2章Windows编程模型X61hInstance是一个从应用程序装载图标资源的实例(后面将详细讨论).
现在将它设置为NULL来装载一个标准的图标.
LpIconName是包含被装载图标资源名称的NULL终止字符串.
当hInstance为NULL时,lpIconName的值如表2.
7所示.
表2.
7LoadIcon()的图标标识符值说明IDI_APPLICATION默认应用程序图标IDI_ASTERISK星号IDI_EXCLAMATION惊叹号IDI_HAND手形图标IDI_QUESTION问号IDI_WINLOGOWindows徽标好,现在我们已介绍了一半的字段了.
做个深呼吸休息一会,让我们进行下一个字段hCursor的介绍.
和hIcon相似,它也是一个图像对象句柄.
不同的是,hCursor是一个指针进入到窗口的用户区才显示的光标句柄.
使用LoadCursor()函数可以得到资源或预定义的系统光标.
我们将在后面讨论资源,简单而言资源就是像位图、光标、图标、声音等一样的数据段,它被编译到应用程序中并可以在运行时进行访问.
Windows类的光标设定如下所示:winclass.
hCursor=LoadCursor(NULL,IDC_ARROW);下面是LoadCursor()函数的原型(表2.
8列出了不同的系统光标标识符):HCURSORlpCursor(HINSTANCEhInstance,//handleofapplicationinstanceLPCTSTRlpCursorName);//icon_namestringoriconresourceidentifierhInstance是你的.
EXE的应用程序实例.
该.
EXE应用程序包含订制光标名称来源的资源.
但现在读者还不能使用该功能,仅将默认的系统光标值设定为NULL.
lpCursorName标识了资源名字符串或资源句柄(我们一般不使用),或者是一个常数,以标识如表2.
8中所示的系统默认值.
表2.
8LoadCursor()的值值说明IDC_ARROW标准箭头IDC_APPSTARTING标准箭头标和小沙漏标IDC_CROSS横标线IDC_IBEAM文本I型标IDC_NO带正斜线的圆圈续表X游戏编程大师X62值说明IDC_SIZEALL四向箭头IDC_SIZENESW指向东北—西南方向的双向箭头IDC_SIZENS指向南北方向的双向箭头IDC_SIZENWSE指向东南—西北方向的双向箭头IDC_SIZEWE指向东西方向的双向箭头IDC_UPARROW垂直方向的箭头IDC_WAIT沙漏现在我们就要解放了,因为我们几乎已经全部介绍完了——剩下的字段更有意义.
让我们看一看hbrBackground.
无论在什么时候绘制或刷新一个窗口,Windows都至少将以用户预定义的颜色或Windows内部设置的画笔颜色填充该窗口的背景.
因此,hbrbackground是一个用于窗口刷新的画笔句柄.
画笔、笔、色彩和图形都是GDI(图形设备接口)的一部分,我们将在下一章中详细讨论.
现在,介绍一下如何申请一个基本的系统画笔来填充窗口.
该项功能由GetStockObject()来实现,如下面程序所示:winclass.
hbrBackground=GetStockObject(WHITE_BRUSH);GetStockObject()是一个通用函数,用于获得Windows系统画笔、笔、调色板或字体的一个句柄.
GetStockObject()只有一个参数,用来指示装载哪一项资源.
表2.
9仅列出了画笔和笔的可能库存对象.
表2.
9GetStockObject()的库存对象标识符值说明BLACK_BRUSH黑色画笔WHITE_BRUSH白色画笔GRAY_BRUSH灰色画笔LTGRAY_BRUSH淡灰色画笔DKGRAY_BRUSH深灰色画笔HOLLOW_BRUSH空心画笔NULL_BRUSH无效(NULL)画笔BLACK_PEN黑色笔WHITE_PEN白色笔NULL_PEN无效(NULL)笔WNDCLASS结构中的下一个字段是lpszMenuName.
它是菜单资源名称的空终止ASCIIX第2章Windows编程模型X63字符串,用于加载和选用窗口.
其工作原理将在第三章"高级Windows编程"中讨论.
现在我们只需将值设为NULL:Winclass.
lpszmenuName=NULL;//thenameofthemenutoattach如我刚提及的那样,每个Windows类代表你的应用程序所创建的不同窗口类型.
在某种程度上,类与模板相似,Windows需要一些途径来跟踪和识别它们.
因此,下一个字段lpszClassName,就用于该目的.
该字段被赋以包含相关类的文本标示符的空终止字符串.
我个人喜欢用诸如"WINCLASS1"、"WINCLASS2"等标示符.
读者以自己喜好而定,以简单明了为原则,如下所示:Winclass.
lpszClassName="WINCLASS1";//thenameoftheclassitself这样赋值以后,你可以使用它的名字来引用这个新的Windows类——很酷,是吗最后就是小应用程序图表.
这是Windows类WNDCLASSEX中新增加的功能,在老版本WNDCLASS中没有.
首先,它是指向你的窗口标题栏和Windows桌面任务栏的句柄.
你经常需要装载一个自定义资源,但是现在只要通过LoadIcon()使用一个标准的Windows图标即可实现:winclass.
hIconSm=LoadIcon(NULL,IDI_APPLICATION);//小图标句柄下面让我们迅速回顾一下整个类的定义:WNDCLASSEXwinclass;//thiswillholetheclasswecreate//firstfillinthewindowclassstructurewinclass.
cbSize=sizeof(WNDCLASSEX);winclass.
style=CS_DBLCLKS|CS_OWNDC|CS_HREDRAW|CS_VREDRAW;winclass.
lpfnWndProc=WindowProc;winclass.
cbClsExtra=0;winclass.
cbWndExtra=0;winclass.
hInstance=hinstance;winclass.
hIcon=LoadIcon(NULL,IDI_APPLICATION);winclass.
hCursor=LoadCursor(NULL,IDC_ARROW);winclass.
hbrBacground=GetStockObject(BLACK_BRUSH);winclass.
lpszMenuName=NULL;winclass.
lpszClassName="WINCLASS";winclass.
hIconSm=LoadIcon(NULL,IDI_APPLICATION);当然,如果想节省一些打字时间的话,可以像下面这样简单地初始化该结构:WNDCLASSEXwinclass={winclass.
cbSize=sizeof(WNDCLASSEX),CS_DBLCLKS|CS_OWNDC|CS_HREDRAW|CS_VREDRAW,WindowProc,0,X游戏编程大师X640,hinstance,LoadIcon(NULL,IDI_APPLICATION),LoadCursor(NULL,IDC_ARROW),GetStockObject(BLACK_BRUSH),NULL,"WINCLASS1",LoadIcon(NULL,IDI_APPLICATION)};这样就省去了许多输入!
注册Windows类现在Windows类已经定义并且存放在winclass中,必须将新的类通知Windows.
该功能通过RegisterClassEx()函数,使用一个指向新类定义的指针来完成,如下所示:RegisterClassEx(&winclass);注意我并没有使用我们例子中的"WINCLASS1"的类名,对于RegisterClassEx()来讲,必须使用保存该类的实际结构,因为在该类调用RegisterClassEx()函数之前,Windows并不知道该类的存在.
明白了吧此外还有一个旧版本的RegisterClass()函数,用于注册基于旧结构WNDCLASS基础上的类.
该类一旦注册,我们就可以任意创建它的窗口.
请看下面如何进行这个工作,然后再详细看一下事件句柄和主事件循环,了解使一个Windows应用程序运行还要做哪些工作.
创建窗口要创建一个窗口(或者一个类窗口的对象),使用CreateWindow()或CreateWindowEx()函数.
后者是更新一点的版本,支持附加类型参数,我们就使用它.
该函数是创建Windows类的函数,我们要多花一点时间来逐行分析.
在创建一个窗口时,必须为这个Windows类提供一个正文名——我们现在就使用"WINCLASS1"命名.
这是识别该Windows类并区别于其他类以及内嵌的诸如按钮、文本框等类型的标示.
下面是CreateWindowEx()函数的原型:HWNDCreateWindowEx(DWORDdwExStyle,//extendedwindowstyleLPCTSTRlpClassName,//pointertoregisteredclassname警告X第2章Windows编程模型X65LPCTSTRlpWindowName,//pointertowindownameDWORDdwStyle,//windowstyleintx,//horizontalpositionofwindowinty,//verticalpositionofwindowintnWidth,//windowwidthintnHeight,//windowheightHWNDhWndParent,//handletoparentorownerwindowHMENUhMenu,//handletomenu,orchildwindowidentifierHINSTANCEhInstance,//handletoapplicationinstanceLPVOIDlpParam);//pointertowindow-creationdata如果该函数执行正确的话,将返回一个指向新建窗口的句柄;否则就返回空值NULL.
上述大多数参数是不需要加以说明的,现在让我们浏览一下:dwExStyle——该扩展类型标志具有高级特征,大多数情况下,可以设为NULL.
如果读者对其取值感兴趣的话,可以查阅Win32SDK帮助,上面有详细的有关该标识符取值的说明.
WS_EX_TOPMOST是我惟一使用过的一个值,该功能使窗口一直保持在上部.
lpClassName——这是你所创建的窗口的基础类名——例如"WINCLASS1".
lpWindowName——这是包含窗口标题的空终止文本字符串——例如"我的第一个窗口".
dwStyle——这是一个说明窗口外观和行为的通用窗口标志——非常重要!
表2.
10列出了一些最常用的值.
当然,可以任意组合使用这些值来得到希望的各种特征.
x,y——这是该窗口左上角位置的像素坐标.
如果你无所谓,可使用CW_USEDEFAULT,这将由Windows来决定.
nWidth,nHeight——这是以像素表示的窗口宽度和高度.
如果你无所谓,可使用CW_USEDEFAULT,这将由Windows来决定.
hWndParent——假如父窗口存在,这是指向父窗口的句柄.
如果没有父窗口,桌面就是父窗口.
hMenu——这是指向附属于该窗口菜单的句柄.
下一章中将详细介绍,现在将其赋值NULL.
hInstance——这是应用程序实例.
这里从WinMain()中使用实例.
lpParam——高级特征,设置为NULL.
表2.
10列出了各种窗口标志设置.
表2.
10dwStyle的通用类型值类型所创建的内容WS_POPUP弹出式窗口WS_OVERLAPPED带有标题栏和边界的重叠式窗口,类似WS_TILED类型续表X游戏编程大师X66类型所创建的内容WS_OVERLAPPEDWINDOW具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MAXIMIZEBOX和WS_MINIMIZEBOX样式的重叠式窗口WS_VISIBLE开始就可见的窗口WS_SYSMENU标题栏上有窗口菜单的窗口WS_BORDER有细线边界的窗口WS_CAPTION有标题栏的窗口(包括WS_BORDER样式)WS_ICONIC开始就最小化的窗口,类似WS_MINIMIZE样式WS_MAXIMIZE开始就最大化的窗口WS_MAXIMIZEBOX具有最大化按钮的窗口.
不能和WS_EX_CONGTEXTHELP样式合并.
WS_SYSMENU也必须指定WS_MINIMIZE开始就最小化的窗口,类似WS_ICONIC样式WS_MINIMIZEBOX具有最小化按钮的窗口.
不能和WS_EX_CONGTEXTHELP样式合并.
WS_SYSMENU也必须指定WS_POPUPWINDOW带有WS_BORDER、WS_POPUP和WS_SYSMENU类型的弹出式窗口WS_SIZEBOX一个窗口边界可以变化,和WS_THICKFRAME类型相同WS_HSCROLL带有水平滚动条的窗口WS_VSCROLL带有垂直滚动条的窗口注意:突出显示的是经常使用的值.
下面是使用标准控件在(0,0)位置创建一个大小为400X400像素的、简单的重叠式窗口.
HWNDhwnd;//windowhandle//createthewindow,bailifproblemif(!
(hwnd=CreateWindowEx(NULL,//extendedstyle"WINCLASS";//class"YourBasicWindow",//titleWS_OVERLAPPEDWINDOW|WS_VISIBLE;0,0//initialx,y400,400//initialwidth,heightNULL,//handletoparentNULL,//handletomenuhinstance,//instanceofthisapplicationNULL)))//extracreationparmsreturn(0);一旦创建了该窗口,它可能是可见或不可见的.
但是,在这个例子中,我们增加了自动X第2章Windows编程模型X67显示的类型标识符WS_VISIBLE.
如果没有添加该标识符,则调用下面的函数来人工显示该窗口://thisshowsthewindowShowWindow(hwnd,ncmdshow);记住WinMain()中的ncmdshow参数了吗这就是使用它的方便之处.
尽管我们使用WS_VISIBLE覆盖了ncmdshow参数,但还是应将其作为一个参数传递给ShowWindow().
下面让Windows更新窗口的内容,并且产生一个WM_PAINT信息,这通过调用函数UpdateWindow()来完成://thissendsaWM_PAINTmessagetowindowandmakes//surethecontentsarerefreshedUpdateWindow();事件处理程序我并不了解你的情况,但注意我现在正使你掌握Windows的核心.
它有如一本神秘小说.
请记住,我所说的事件处理程序就是当事件发生时Windows从主事件循环调用的回调函数.
回顾一下图2.
6,刷新一下你对通用数据流的印象.
事件处理器由读者自己编写,它能够处理你所关心的所有事件.
其余的工作就交给Windows处理.
当然,请记住,你的应用程序所能处理的事件和消息越多,它的功能越强.
在编写程序之前,让我们讨论一下事件处理器的一些细节,即事件处理器能做什么,工作机理如何.
首先,对于创建的任何一个Windows类,都有一个独立的事件处理器,我指的是Windows'Procedure,从现在开始简称WinProc.
当收到用户或Windows发送的消息并放在主事件序列中时,WinProc就接收到主事件循环发送的消息.
这简直是一个智力绕口令,我换个方式来说明……当用户和Windows运行任务时,你的窗口和/或其他应用程序窗口产生事件和消息.
所有消息都进入一个队列,而你的窗口的消息发送到你的窗口专用队列中.
然后主事件循环检索这些消息,并且将它们发送到你的窗口的WinProc中来处理.
这几乎有上百个可能的消息和变量,因此,我们就不全部分析了.
值得庆幸的是,你只需处理很少的消息和变量,就可以启动并运行Windows应用程序.
简单地说,主事件循环将消息和事件反馈到WinProc,WinProc对它们进行处理.
因此不仅你要关注WinProc,主事件循环同样也要关心WinProc.
现在我们简要地了解一下WinProc,现假定WinProc只接收消息.
现在来看一下WinProc的工作机制,让我们看一下它的原型:X游戏编程大师X68LRESULTCALLBACKWindowProc(HWNDhwnd,//windowhandleofsenderUINTmsg,//themessageidWPARAMwparam,//furtherdefinesmessageLPARAMlparam);//furtherdefinesmessage当然,这仅仅是回调函数的原型.
只要将函数地址作为一个函数指针传递给winclass.
lpfnWndProc,就可以调用该函数的任何信息,如下所示:winclass.
lpfnWndProc=WindowProc;参数的含义是不言自明的:hwnd——这是一个Windows句柄,只有当你使用同一个Windows类打开多个窗口时它才用到.
这种情况下,hwnd是表明消息来自哪个窗口的惟一途径.
图2.
7表示了这种情况.
图2.
7使用同一个类打开的多个窗口msg——这是一个实际的WinProc处理的消息标识符.
这个标识符可以是众多主要消息中的一个.
Wparam和lparam——进一步限定或细分发送到msg参数中的信息.
最后,我们感兴趣的是返回类型LRESULT和声明说明符CALLBAK.
这些关键字都是必需的,不能忽略它们.
因此大多数人所要做的就是使用switch()来处理msg所表示的消息,然后为每一种情况编写代码.
在msg的基础上,你可以知道是否需要进一步求wparam和/或lparam的值.
很酷吗因此让我们看一下由WinProc传递过来的所有可能的消息,然后再看一下WinProc的工作机理.
表2.
11简要列出了一些基本的消息说明符.
表2.
11消息说明符的简表窗口1"Winclass1"的WinProc()事件处理程序若Windows类相同则所有消息均由同样的事件处理程序处理消息窗口2消息窗口3消息同样的应用程序X第2章Windows编程模型X69值说明WM_ACTIVATE当窗口被激活或者成为一个焦点时传递WM_CLOSE当窗口关闭时传递WM_CREATE当窗口第一次创建时传递WM_DESTROY当窗口可能要被破坏时传递WM_MOVE当窗口移动时传递WM_MOUSEMOVE当移动鼠标时传递WM_KEYUP当松开一个键时传递WM_KEYDOWN当按下一个键时传递WM_TIMER当发生定时程序事件时传递WM_USER允许传递消息WM_PAINT当一个窗口需重画时传递WM_QUIT当Windows应用程序最后结束时传递WM_SIZE当一个窗口改变大小时传递要认真看表2.
11,了解所有消息的功能.
在应用程序运行时将有一个或多个上述消息传递到WinProc.
消息说明符本身在msg中,而其他信息都存储在wparam和lparam中.
因此参考在线Win32SDK帮助来了解某个消息的参数所代表的意思是个不错的方法.
幸好我们现在只对下面三个消息感兴趣:WM_CREATE——当窗口第一次创建时传递该消息,以便你进行启动、初始化或资源配置工作.
WM_PAINT——当一个窗口内容需重画时传递该消息.
这可能有许多原因:用户移动窗口或改变其尺寸、弹出其他应用程序而遮挡了你的窗口不清楚等.
WM_DESTROY——当窗口可能要被破坏时该消息传递到你的窗口.
通常这是由于用户单击该窗口的关闭按钮,或者是从该窗口的系统菜单中关闭该窗口造成的.
无论上述哪一种方式,都应当释放所有的资源,并且通过发送一个WM_QUIT消息来通知Windows完全终止应用程序.
后面还将详细介绍.
OK!
让我们看一看WinProc处理这些消息的整个过程.
LRESULTCALLBACKWindowProc(HWNDhwnd,UINTmsg,WPARAMwparam,LPARAMlparam);{//thisisthemainmessagehandlerofthesystemPAINTSTRUCTps;//UsedinWM_PAINTHDChdc;//handletoadevicecontextX游戏编程大师X70//whatisthemessageswitch(msg){caseWM_CREATE;{//doinitializationstuffhere//returnsuccessreturn(0)}break;caseWM_PAINT;{//simplyvalidatethewindowhdc=BeginPaint(hwnd,&ps);//youwoudledoallyourpaintinghereEndPaint(hwnd,&ps);//returnsuccessreturn(0)}break;caseWM_DESTROY;{//Killtheapplication,thissendsaWM_QUITmessagePostQuitMessage(0);//returnsuccessreturn(0)}break;default:break;}//endswitch//processanymessagethatwedidn'ttakecareofreturn(DefWindowProc(hwnd,msg,wparam,lparam));}//endWinProc由上面可以看到,函数的大部分是由空白区构成——这真是件好事情.
让我们就以WM_CREATE处理程序开始吧.
该函数所作的一切就是return(0).
这就是通知Windows由编程人员自己处理该函数,因此无需更多的操作.
当然,也可以在WM_CREATE消息中进行全部的初始化工作,但那是你的事了.
下一个消息WM_PAINT非常重要.
该消息在窗口需要重画时被发送.
一般来说这表示你应当进行重画工作.
对于DirectX游戏来说,这并不是件什么大事,因为你将以30到60帧/秒的频率来重画屏幕.
但是对于标准Windows应用程序来说,它就是件大事了.
我将在X第2章Windows编程模型X71后面章节中更详细地介绍WM_PAINT,目前的功能就是通知Windows,你要重画该窗口了,因此就停止发送WM_PAINT消息.
要完成该功能,你必须激活该窗口的客户区.
有许多方法可以做到,但调用函数BeginPaint()和EndPaint()最简单.
这一对调用将激活窗口,并使用原先存储在Windows类中的变量hbrBackground的背景刷来填充背景.
下面是程序代码://beginpaintinghdc=BeginPaint(hwnd,&ps);//youwoulddoallyourpaintinghereEndPaint(hwnd,&ps);下面要提醒几件事情.
第一,请注意,每次调用的第一个参数是窗口句柄hwnd.
这是一个非常必要的参数,因为BeginPaint—EndPaint函数能够在任何应用程序窗口中绘制,因此该窗口句柄指示了要重画哪个窗口.
第二个参数是包含必须重画矩形区域的PAINTSTRUCT结构的地址.
下面是PAINTSTRUCT结构:typedefstructtagPAINTSTRUCT{HDChdc;BOOLfErase;RECTrcPaint;BOOLfRestore;BOOLfIncUpdate;BYTErgbReserved[32];}PAINTSTRUCT;实际上不需要关心这个函数,当我们讨论图形设备接口时会再讨论这个函数.
其中最重要的字段就是rcPaint.
图2.
8表示了这个字段的内容.
注意Windows一直尽可能地试图作最少的工作,因此当一个窗口内容破坏之后,Windows至少会告诉你要恢复该内容并能够重画的最小的矩形.
如果你对矩形结构感兴趣的话,会发现只有矩形的四个角是最重要的,如下所示:typedefstructtagRECT{LONGleft;//leftx-edgeofrectLONGtop;//topy-edgeofrectLONGright;//rightx-edgeofrectLONGbottom;//bottomy-edgeofrect}RECT;调用BeginPaint()函数应注意的最后一件事情是,它返回一个指向图形环境或hdc的句柄:HDChdc;//handletographicscontextHdc=BeginPaint(hwnd,&ps);X游戏编程大师X72图2.
8仅重新绘制无效区图形环境就是描述视频系统和正在绘制表面的数据结构.
奇妙的是,如果你需要绘制图形的话,只要获得一个指向图形环境的句柄即可.
这便是关于WM_PAINT消息的内容.
WM_DESTROY消息实际上非常有意思.
WM_DESTROY在用户关闭窗口时被发送.
当然仅仅是关闭窗口,而不是关闭应用程序.
应用程序继续运行,但是没有窗口.
对此要进行一些处理.
大多数情况下,当用户关闭主要窗口时,也就意味着要关闭该应用程序.
因此,你必须通过发送一个消息来通知系统.
该消息就是WM_QUIT.
因为该消息经常使用,所以有一个函数PostQuitMessage()来替你完成发送工作.
在Wm_DESTROY处理程序中你所要做的就是清除一切,然后调用PostQuitMessage(0)通知Windows终止应用程序.
接着将WM_QUIT置于消息队列,这样在某一个时候终止主事件循环.
在我们所分析的WinProc句柄中还有许多细节应当了解.
首先,你肯定注意到了每个处理程序体后面的return(0).
它有两个目的:退出WinProc以及通知Windows你已处理的信息.
第二个重要的细节是默认消息处理程序DefaultWindowProc().
该函数是一个传递Windows默认处理消息的传递函数.
因此,如果不处理该消息的话,可通过如下所示的调用来结束你的所有事件处理函数://processanymessagesthatwedidn'ttakecareofreturn(DefWindowProc(hwnd,msg,wparam,lparam));我认为代码本身过多并且过于麻烦.
然而,一旦你有了一个基本Windows应用程序架构的话,你只要将它复制并在其中添加你自己的代码就行了.
正如我所说的那样,我的主要目标是帮助你创建一个可以使用的类DOS32的游戏操作台,并且几乎忘记了任何正在运行标题栏窗口可能另一窗口使该区域无效只重画该区域由WM_PAINT处理客户区域X第2章Windows编程模型X73的Windows工作.
让我们转到下一部分——主事件循环.
主事件循环最难的一部分终于结束了.
我正要脱口而出:主事件循环太简单了.
下面讨论一下://entermaineventloopwhile(GetMessage(&msg,NULL,0,0)){//translateanyacceleratorkeysTranslateMessage(&msg);//sendthemessagetothewindowprocDispatchMessage(&msg);}//endwhile这是什么OK!
让我们来研讨一下.
只要GetMessage()返回一个非零值,主程序while()就开始执行.
GetMessage()是主事件循环的关键代码,其惟一的用途就是从事件队列中获得消息,并进行处理.
你会注意到GetMessage()有四个参数.
第一个参数对我们非常重要,而其余的参数都可以设置为NULL或0.
下面列出其原型,以供参考:BOOLGetMessage(LPMSGlpMsg,//addressofstructurewithmessageHWNDhWnd,//handleofwindowUINTwMsgFilterMin,//firstmessageUINTwMsgFilterMax);//lastmessagemsg参数是Windows放置下一个消息的存储器.
但是和WinProc()的msg参数不同的是,该msg是一个复杂的数据结构,而不仅仅是一个整数.
当一个消息传递到WinProc时,它就被处理并分解为各个组元.
MSG的结构如下所示:typedefstructtagMSG{HWNDhwnd;//windowwheremessageoccurredUINTmessage;//messageiditselfWPARAMwParam;//subqualifiesmessageLPARAMlParam;//subqualifiesmessageDWORDtime;//timeifmessageeventPOINTpt;//positionofmouse}MSG;看出点眉目来了,是吗注意所有向WinProc()传递的参数都包含在该结构中,还包括其他参数,如事件发生时的时间和鼠标的位置.
GetMessage()从时间序列中获得下一个消息,然后下一个被调用的函数就是X游戏编程大师X74TranslateMessage().
TranslateMessage()是一个虚拟加速键转换器——换句话说就是输入工具.
现在只是调用它,不必管其功能.
最后一个函数DispatchMessage()指出所有操作发生的位置.
当消息被GetMessage()获得以后,由函数TranslateMessage()稍加处理和转换,通过函数DispatchMessage()调用WinProc进行进一步的处理.
DispatchMessage()调用WinProc,并从最初的MSG结构中传递适当的参数.
图2.
9表示了整个处理过程的最后部分.
图2.
9事件循环消息处理机制这样,你就成了Windows专家了!
如果你已经理解了上面刚刚讨论过的概念以及事件循环、事件处理程序等等的重要性,那你至少已经掌握了90%的内容了.
剩下的就是一些细节问题了.
程序清单2.
3是一个完整的Windows程序,内容是创建一个窗口,并等候关闭.
程序清单2.
3一个基本的Windows程序//DEMO2_3.
CPP-Acompletewindowsprogram//INCLUDES#defineWIN32_LEAN_AND_MEAN//justsaynotoMFC#include//includeallthewindowsheaders#include//includeusefulmacros#include#include//DEFINES//definesforwindows事件句柄重进窗口用户输入消息队列游戏逻辑人工智能渲染物理系统X第2章Windows编程模型X75#defineWINDOW_CLASS_NAME"WINCLASS1"//GLOBALS//FUNCTIONSLRESULTCALLBACKWindowProc(HWNDhwnd,UINTmsg,WPARAMwparam,LPARAMlparam){//thisisthemainmessagehandlerofthesystemPAINTSTRUCTps;//usedinWM_PAINTHDChdc;//handletoadevicecontext//whatisthemessageswitch(msg){caseWM_CREATE:{//doinitializationstuffhere//returnsuccessreturn(0);}break;caseWM_PAINT:{//simplyvalidatethewindowhdc=BeginPaint(hwnd,&ps);//youwoulddoallyourpaintinghereEndPaint(hwnd,&ps);//returnsuccessreturn(0);}break;caseWM_DESTROY:{//killtheapplication,thissendsaWM_QUITmessagePostQuitMessage(0);//returnsuccessreturn(0);}break;default:break;}//endswitchX游戏编程大师X76//processanymessagesthatwedidn'ttakecareofreturn(DefWindowProc(hwnd,msg,wparam,lparam));}//endWinProc//WINMAINintWINAPIWinMain(HINSTANCEhinstance,HINSTANCEhprevinstance,LPSTRlpcmdline,intncmdshow){WNDCLASSEXwinclass;//thiswillholdtheclasswecreateHWNDhwnd;//genericwindowhandleMSGmsg;//genericmessage//firstfillinthewindowclassstructurewinclass.
cbSize=sizeof(WNDCLASSEX);winclass.
style=CS_DBLCLKS|CS_OWNDC|CS_HREDRAW|CS_VREDRAW;winclass.
lpfnWndProc=WindowProc;winclass.
cbClsExtra=0;winclass.
cbWndExtra=0;winclass.
hInstance=hinstance;winclass.
hIcon=LoadIcon(NULL,IDI_APPLICATION);winclass.
hCursor=LoadCursor(NULL,IDC_ARROW);winclass.
hbrBackground=GetStockObject(BLACK_BRUSH);winclass.
lpszMenuName=NULL;winclass.
lpszClassName=WINDOW_CLASS_NAME;winclass.
hIconSm=LoadIcon(NULL,IDI_APPLICATION);//registerthewindowclassif(!
RegisterClassEx(&winclass))return(0);//createthewindowif(!
(hwnd=CreateWindowEx(NULL,//extendedstyleWINDOW_CLASS_NAME,//class"YourBasicWindow",//titleWS_OVERLAPPEDWINDOW|WS_VISIBLE,0,0,//initialx,y400,400,//initialwidth,heightNULL,//handletoparentNULL,//handletomenuhinstance,//instanceofthisapplicationNULL)))//extracreationparmsreturn(0);X第2章Windows编程模型X77//entermaineventloopwhile(GetMessage(&msg,NULL,0,0)){//translateanyacceleratorkeysTranslateMessage(&msg);//sendthemessagetothewindowprocDispatchMessage(&msg);}//endwhile//returntoWindowslikethisreturn(msg.
wParam);}//endWinMain要编译DEMO2_3.
CPP,只需创建一个Win32环境下的.
EXE应用程序,并且将DEMO2_3.
CPP添加到项目中即可.
假如你喜欢的话,可以直接从CD-ROM上运行预先编译好的程序DEMO2_3.
EXE.
图2.
10显示了运行中的该程序.
图2.
10运行中的DEMO2_3.
EXE在进行下一部分内容之前,我还有事情要说.
首先,如果你认真阅读了事件循环的话,会发现它看上去并不是个实时程序.
也就是说,当程序在等待通过GetMessage()传递的消息的同时,主事件循环基本上是锁定的.
这的确是真的;你必须以各种方式来避免这种现象,因为你需要连续地运行你的游戏处理过程,并且在Windows事件出现时处理这些事件.
产生一个实时事件循环这种实时的无等候的事件循环很容易实现.
你所需要的就是一种测试在消息序列中是否X游戏编程大师X78有消息的方法.
如果有,你就处理它;否则,继续处理其他的游戏逻辑并重复进行.
运行的测试函数是PeekMessage().
其原型几乎和GetMessage()相同,如下所示:BOOLPeekMessage(LPMSGlpMsg,//pointertostructureformessageHWNDhWnd,//handletowindowUINTwMsgFilterMin,//firstmessageUINTwMsgFilterMax,//lastmessageUINTwRemoveMsg);//removalflags如果有可用消息的话返回值非零.
区别在于最后一个参数,它控制如何从消息序列中检索消息.
对于wRemoveMsg,有效的标志有:PM_NOREMOVE——PeekMessage()处理之后,消息没有从序列中去除.
PM_REMOVE——PeekMessage()处理之后,消息已经从序列中去除.
如果将这两种情况考虑进去的话,你可以做出两个选择:使用PeekMessage()和PM_NOREMOVE,如果有消息的话,就调用GetMessage();另一种选择是:使用PM_REMOVE,如果有消息则使用PeekMessage()函数本身来检索消息.
一般使用后一种情况.
下面是核心逻辑的代码,我们在主事件循环中稍作改动以体现这一新技术:while(TRUE){//testifthereisamessageinqueue,ifsogetitif(PeekMessage(&msg,NULL,0,0,PM_REMOVE){//testifthisaquitif(msg.
message==WM_QUIT)break;//translateanyacceleratorkeysTranslateMessage(&msg);//sendthemessagetothewindowprocDispatchMessage(&msg);}//endif//maingameprocessinggoeshereGame_Main();}//endwhile我已经将程序中的重要部分突出显示.
粗体的第一部分内容是:if(msg.
message==WM_QUIT)break;下面是如何测试从无限循环体while(true)中退出.
请记住,当在WinProc中处理WM_DESTROY消息时,你的工作就是通过调用PostQuitMessage()函数来传递WM_QUIT消息.
WM_QUIT就在事件序列中慢慢地移动,你可以检测到它,所以可以跳出主循环.
X第2章Windows编程模型X79突出显示的程序最后一部分指出调用主游戏程序代码循环的位置.
但是请不要忘记,在运行一幅动画或游戏逻辑之后,调用Game_Main()或者调用任意程序必须返回.
否则,Windows主事件循环将不处理消息.
这种新型的实时结构的例子非常适合于游戏逻辑处理程序,请看源程序DEMO2_4.
CPP以及CD-ROM上相关的DEMO2_4.
EXE.
这种结构实际上是本书剩下部分的模型.
打开多个窗口在完成本章内容之前,我想讨论一个你可能非常关心的更重要的话题——如何打开多个窗口.
实际上,这是小事一桩,其实你已经知道如何打开多个窗口.
你所需要做的就是多次调用函数CreateWindowEx()来创建这些窗口,事实也的确如此.
但是,对此还有一些需要注意的问题.
首先,请记住当你创建一个窗口时,是建立在Windows类的基础之上的.
在该类中定义了WinProc或者整个类的事件处理程序.
这是非常重要的细节,因此应当注意.
你可以使用同一个类来创建多个窗口,这些窗口的所有消息都要传递到同一个WinProc中,正如由WINCLASSEX结构中定义lpfnWndProc字段指向的事件处理程序一样.
图2.
11表示了这种情况下的消息流.
图2.
11使用相同的Windows类打开多个窗口的消息流这样可能达到你的愿望,也可能达不到.
如果想应用不同的WinProc打开各个窗口的话,你必须创建多个Windows类,并且使用每一个类创建各个窗口.
这样每一个类的窗口就指向各自的WinProc并向其传递消息.
图2.
12表示了这种情况.
"Windows类"事件处理程序消息X游戏编程大师X80图2.
12多个窗口的多个Windows类了解了这些内容后,下面是基于同一个类来创建多个窗口的程序代码://createthefirstwindowif(!
(hwnd=CreateWindowEx(NULL,//extendedstyleWINDOW_CLASS_NAME,//class"Window1BasedonWINCLASS1",//titleWS_OVERLAPPEDWINDOW|WS_VISIBLE,0,0,//initialx,y400,400//initialwidth,heightNULL,//handletoparentNULL,//handletomenuhinstance,//instanceofthisapplicationNULL)))//extracreationparmsreturn(0);//createthesecondwindowif(!
(hwnd=CreateWindowEx(NULL,//extendedstyleWINDOW_CLASS_NAME,//class"Window2AlsoBasedonWINCLASS1",//titleWS_OVERLAPPEDWINDOW|WS_VISIBLE,100,100,//initialx,y400,400//initialwidth,heightNULL,//handletoparentNULL,//handletomenuhinstance,//instanceofthisapplicationNULL)))//extracreationparmsreturn(0);当然,你可能希望用不同的变量而不是同一个变量来跟踪每一个窗口,就像hwnd中的例子那样,其实你已掌握了其要义,如一次打开两个窗口的例子.
请看DEMO2_5.
CPP以及CD-ROM上相关的可执行文件DEMO2_5.
EXE.
当你运行.
EXE执行文件时,应当可以看到多个窗口消息队列窗口1消息队列窗口2消息被发送至基于Windows类的各个WinProc()X第2章Windows编程模型X81如图2.
13所示的情况.
注意当你关闭任何一个窗口时,两个窗口将同时关闭,并且应用程序也终止.
看一看是否能够找到每次只关闭一个窗口的方法.
(提示:创建两个Windows类,直到两个窗口都关闭之后再传递WM_QUIT消息.
)图2.
13多窗口程序DEMO2_5.
EXE总结尽管我并不了解你,但是我还是很为你激动!
到目前为止,你已具备了Windows编程的基本知识并需要开始掌握更复杂的Windows编程知识.
你已了解了Windows和多任务的结构,并且也知道了如何创建一个Windows类、注册类、创建窗口、编写事件循环和句柄等等内容.
因此你已经打下了坚实的Windows编程基础.
你完成了一项最杰出的任务.
下一章中,我们将了解更多的和Windows相关的内容,如:使用资源、创建菜单、使用对话框以及获取信息等.

乌云数据(10/月),香港cera 1核1G 10M带宽/美国cera 8核8G10M

乌云数据主营高性价比国内外云服务器,物理机,本着机器为主服务为辅的运营理念,将客户的体验放在第一位,提供性价比最高的云服务器,帮助各位站长上云,同时我们深知新人站长的不易,特此提供永久免费虚拟主机,已提供两年之久,帮助了上万名站长从零上云官网:https://wuvps.cn迎国庆豪礼一多款机型史上最低价,续费不加价 尽在wuvps.cn香港cera机房,香港沙田机房,超低延迟CN2线路地区CPU...

易探云香港vps主机价格多少钱?香港云服务器主机租用价格

易探云香港vps主机价格多少钱?香港vps主机租用费用大体上是由配置决定的,我们选择香港vps主机租用最大的优势是免备案vps。但是,每家服务商的机房、配置、定价也不同。我们以最基础配置为标准,综合比对各大香港vps主机供应商的价格,即可选到高性能、价格适中的香港vps主机。通常1核CPU、1G内存、2Mbps独享带宽,价格在30元-120元/月。不过,易探云香港vps主机推出四个机房的优惠活动,...

妮妮云(119元/季)日本CN2 2核2G 30M 119元/季

妮妮云的知名度应该也不用多介绍了,妮妮云旗下的云产品提供商,相比起他家其他的产品,云产品还是非常良心的,经常出了一些优惠活动,前段时间的八折活动推出了很多优质产品,近期商家秒杀活动又上线了,秒杀产品比较全面,除了ECS和轻量云,还有一些免费空间、增值代购、云数据库等,如果你是刚入行安稳做站的朋友,可以先入手一个119/元季付的ECS来起步,非常稳定。官网地址:www.niniyun.com活动专区...

ignore_user_abort为你推荐
permissiondeniedpermission denied是什么意思啊?中老铁路中国有哪些正在修的铁路18comic.funAnime Comic Fun是什么意思啊 我不懂英文百花百游百花净斑方效果怎么样?百度关键词工具常见百度关键词挖掘方法分别是什么请列举?www.qq530.com谁能给我一个听歌的网站?baqizi.cc讲讲曾子杀猪的主要内容!www.toutoulu.comWWW【toutoulu】cOM怎么搜不到了?到哪里能看到toutoulu视频?19ise.com欲火难耐看什么电影 19部性感至极的佳片www.mfav.org海关编码在线查询http://www.ccpit.org.c
虚拟主机提供商 深圳主机租用 美国linux主机 动态域名解析软件 域名解析文件 优惠码 512m 轻博 debian6 好看的桌面背景图 长沙服务器 ftp教程 免费活动 上海联通宽带测速 备案空间 web应用服务器 论坛主机 阿里云手机官网 网站加速 存储服务器 更多