1. 开始
正如您可能知道的,Windows 98已成为使用Intel 32位元微处理器(例如486和Pentium)的IBM相容型个人电脑环境上最新的图形作业系统之代表。Windows NT是IBM PC相容机种以及一些RISC(精简指令集电脑)工作站上使用的Windows工业增强型版本。
使用本书有三个先决条件。首先,您应该从使用者的角度熟悉Windows 98。不要期望可以在不了解Windows使用者介面的情形下开发其应用程式。因此,我建议您在开发程式(或在进行其他工作)时使用执行Windows的机器来跑Windows应用程式。
第二,您应了解C语言。如果要写Windows程式,一开始却不想了解C语言,那不是一个好主意。我建议您在文字控制台环境中,例如在Windows 98 MS-DOS命令提示视窗下提供的环境中学习C语言。Windows程式设计有时包括一些非文字模式程式设计的C语言部分;在这些情况下,我将针对这些问题提供讨论。但大多数情况下,您应非常熟悉该语言,特别是C语言的结构和指标。了解标准C语言执行期程式库的一些相关知识是有帮助的,但不是必要的。
第三,您应该在机器上安装一个适於进行Windows程式设计的32位元C语言编译器和开发环境。在本书中,假定您正在使用Microsoft Visual C++ 6.0,该套装软体可独立购买,也可作为Visual Studio 6.0套装软体的一部分购买。
到此为止,我将不再假设您具有任何图形使用者介面(如Windows)的程式写作经验。
WINDOWS环境
Windows几乎不需要介绍。然而人们很容易忘记Windows给办公室和家庭桌上型电脑所带来的重大改变。Windows在其早期曾经走过一段坎坷的道路,征服桌上型电脑市场的前途一度相当渺茫。
Windows简史
在1981年秋天IBM PC推出之後不久,MS-DOS就已经很明显成为PC上的主流作业系统。MS-DOS代表Microsoft Disk Operating System(磁碟作业系统)。MS-DOS是一个小型的作业系统。MS-DOS提供给用户一种命令列介面,提供如DIR和TYPE的命令,也可以将应用程式载入记忆体执行。对於应用程式写作者,它提供了一组函式呼叫,进行档案的输入输出(I/O )。对於其他的周边处理-尤其是将文字或图形写到显示器上-应用程式可以直接存取PC的硬体。
由於记忆体和硬体的限制,成熟的图形环境缓慢地才到来。当苹果电脑公司不幸的Lisa电脑在1983年1月发表时,它提供了不同於文字模式环境的另一种选择,并在1984年1月成为Macintosh上图形环境的一种标准。尽管Macintosh的市场占有率在下降,但是它仍然被认为是衡量所有其他图形环境的标准。包括Macintosh和Windows的所有图形环境,其实都要归功於Xerox Palo Alto Research Center(PARC)在70年代中期所作的开拓性研究工作。
Windows是由微软在1983年11月(在Lisa之後,Macintosh之前)宣布,并在两年後(1985年11月)发行。在此後的两年中,紧随著Microsoft Windows早期版本1.0之後,又推出了几种改进版本,以支援国际商业市场,并提供新型视讯显示器和印表机的驱动程式。
Windows版本2.0是在1987年11月正式在市场上推出的。该版本对使用者介面做了一些改进。这些改进中最有效的是使用了可重叠式视窗,而Windows 1.0中使用的是并排式视窗。Windows 2.0还增强了键盘和滑鼠介面,特别是加入了功能表和对话方块。
至此,Windows还只要求Intel 8086或者8088等级的微处理器,以「实际模式」执行,只能存取位址在1MB以下的记忆体。Windows/386(在Windows 2.0之後不久发行的)使用Intel 386微处理器的「虚拟8086」模式,实现将直接存取硬体的多个MS-DOS程式视窗化和多工化。为了统一起见,Windows版本2.1被更名为Windows/286。
Windows 3.0是在1990年5月22日发表的。它将Windows/286和Windows/386结合到同一种产品中。Windows 3.0有了一个很大的改变,这就是对Intel的286、386和486微处理器保护模式的支援。这能使Windows和Windows应用程式能存取高达16MB的记忆体。Windows用於执行程式和维护档案的「外壳」程式得到了全面的改进。Windows 3.0是第一个在家用和办公室市场上取得立足点的版本。
任何Windows的历史介绍都必须包括一些OS/2的说明,OS/2是对DOS和Windows的另一种选择,最初是由Microsoft和IBM合作开发的。OS/2版本1.0(只有文字模式)在Intel 286(或者後来的)微处理器上运行,在1987年末发布。在1988年10月的OS/2版本1.1中出现了管理图形使用者介面的PM(Presentation Manager)。PM最初的设计构想是成为Windows的一种保护模式版本,但是图形API改变程度太大,致使软体生产厂商很难提供对这两种平台的支援。
到1990年9月,IBM和Microsoft之间的冲突达到了高峰,导致这两个公司最後分道扬镳。IBM接管了OS/2,而Microsoft明确表示Windows将是他们作业系统策略的中心。虽然OS/2仍然拥有一些狂热的崇拜者,但是它远不及Windows这样的普及程度。
Microsoft Windows版本3.1是1992年4月发布的,其中包括的几个重要特性是TrueType字体技术(给Windows带来可缩放的轮廓字体 )、多媒体(声音和音乐 )、物件连结和嵌入(OLE:Object Linking and Embedding)和通用对话方块。跟OS/2一样,Windows 3.1只能在保护模式下运作,并且要求至少配置了1MB记忆体的286或386处理器。
在1993年7月发表的Windows NT是第一个支援Intel 386、486和Pentium微处理器32位元保护模式的Windows版本。Windows NT提供32位元平坦定址,并使用32位元的指令集。(本章後面我会谈到一些定址空间的问题 )。Windows NT还可以移植到非Intel处理器上,并在几种使用RISC晶片的工作站上执行。
Windows 95是在1995年8月发布的。和Windows NT一样,Windows 95也支援Intel 386或更高等级处理器的32位元保护模式。虽然它缺少Windows NT中的某些功能,诸如高安全性和对RISC机器的可携性等,但是Windows 95具有需要较少硬体资源的优点。
Windows 98在1998年6月发布,具有许多加强功能,包括执行效能的提高、更好的硬体支援以及与网际网路和全球资讯网(WWW)更紧密的结合。
Windows方面
Windows 98和Windows NT都是支援32位元优先权式多工(preemptive multitasking)及多执行绪的图形作业系统。Windows拥有图形使用者介面(GUI ),这种使用者介面也称作「视觉化介面」或「图形视窗环境」。有关GUI的概念可追溯至70年代中期,在Alto和Star等机器上以及SmallTalk等环境中由Xerox PARC所作的研究工作。该项研究的成果後来被Apple Computer和Microsoft引入主流并流行起来。虽然有一些争议,但现在已非常清楚,GUI是(Microsoft的Charles Simonyi的说法)一个在个人电脑工业史上集各方面技术大成於一体的最重要产物。
所有GUI都在点矩阵对应的视讯显示器上处理图形。图形提供了使用萤幕的最佳方式、传递资讯的视觉化丰富多彩环境,以及能够WYSIWYG(what you see is what you get:所见即所得)的图形视讯显示和为书面文件准备好格式化文字输出内容。
在早期,视讯显示器仅用於回应使用者通过键盘输入的文字。在图形使用者介面中,视讯显示器自身成为使用者输入的一个来源。视讯显示器以图示和输入设备(例如按钮和卷轴)的形式显示多种图形物件。使用者可以使用键盘(或者更直接地使用滑鼠等指向装置)直接在萤幕上操纵这些物件,拖动图形物件、按下滑鼠按钮以及滚动卷轴。
因此,使用者与程式的交流变得更为亲密。这不再是一种从键盘到程式,再到视讯显示器的单向资讯流动,使用者已经能够与显示器上的物件直接交互作用了。
使用者不再需要花费长时间学习如何使用电脑或掌握新程式了。Windows让这一切成真,因为所有应用程式都有相同的基本外观和感觉。程式占据一个视窗-萤幕上的一块矩形区域。每个视窗由一个标题列标识。大多数程式功能由程式的功能表开始。用户可使用卷轴观察那些无法在一个萤幕中装下的资讯。某些功能表项目触发对话方块,用户可在其中输入额外的资讯。几乎在每个大的Windows程式中都有一个用於开启档案的特殊对话方块。该对话方块在所有这些Windows程式中看起来都一样(或接近相同),而且几乎总是从同一功能表选项中启动。
一旦您了解使用一个Windows程式的方法,您就非常容易学习其他的Windows程式。功能表和对话方块允许用户试验一个新程式并探究它的功能。大多数Windows程式同时具有键盘介面和滑鼠介面。虽然Windows程式的大多数功能可通过键盘控制,但使用滑鼠要容易得多。
从程式写作者的角度看,一致的使用者介面来自於Windows建构功能表和对话方块的内置程式。所有功能表都有同样的键盘和滑鼠介面,因为这项工作是由Windows处理,而不是由应用程式处理。
为便於多个程式的使用,以及这些程式间资讯的交换,Windows支援多工。在同一时刻能有多个Windows程式显示并运行。每个程式在萤幕上占据一个视窗。用户可在萤幕上移动视窗,改变它们的大小,在不同程式间切换,并从一个程式向另一个程式传送资料。因为这些视窗看起来有些像桌面上的纸(当然,这是电脑还未占据办公桌之前的年代),Windows有时被称作:一个显示多个程式的「具象化桌面」。
Windows的早期版本使用一种「非优先权式(non-preemptive)」的多工系统。这意味著Windows不使用系统计时器将处理时间分配给系统中运行的多个应用程式,程式必须自愿放弃控制以便其他程式运行。在Windows NT和Windows 98中,多工是优先权式的,而且程式自身可分割成近乎同时执行的多个执行绪。
作业系统不对记忆体进行管理便无法实现多工。当新程式启动、旧程式终止时,记忆体会出现碎裂空间。系统必须能够将闲置的记忆体空间组织在一起,因此系统必须能够移动记忆体中的程式码和资料块。
即使是在8088微处理器上跑的Windows 1.0也能进行这类记忆体管理。在实际模式限制下,这种能力被认为是软体工程一个令人惊讶的成就。在Windows 1.0中,PC硬体结构的640KB记忆体限制,在不要求任何额外记忆体的情况下被有效地扩展了。但Microsoft并未就此停步:Windows 2.0允许Windows应用程式存取延伸记忆体(EMS);Windows 3.0在保护模式下,允许Windows应用程式存取高达16MB的扩展记忆体。Windows NT和Windows 98通过成熟的32位元作业系统及平坦定址空间,摆脱了这些旧的限制。
Windows上执行的程式可共用在称为「动态连结程式库」的档案中的常式。Windows包括一个机制,能够在执行时连结使用动态连结程式库中常式的程式。Windows自身基本上就是一个动态连结程式库的集合。
Windows是一个图形介面,Windows程式能够在视讯显示器和印表机上充分利用图形和格式化文字。图形介面不仅在外观上更有吸引力,而且还能够让使用者传递高层次的资讯。
Windows应用程式不能直接存取萤幕和印表机等图形显示设备硬体。相反,Windows提供一种图形程式语言(称作图形装置介面,或者GDI),使显示图形和格式化文字更容易。Windows虚拟化了显示硬体,使为Windows编写的程式可使用任何具有Windows装置驱动程式的视频卡或印表机,而程式无需确定系统相连的装置类型。
对Windows开发者来说,将与装置无关的图形介面输出到IBM PC上不是件轻松的事。PC的设计是基於开放式架构的原则,鼓励第三方硬体制造商为PC开发周边设备,而且开发了大量这样的设备。虽然出现了多种标准,PC上的传统MS-DOS程式仍不得不各自支援许多不同的硬体设备。这对MS-DOS文字处理软体来说非常普遍,它们连同1到2张有许多小档案的磁片一同销售,每个档案支援一种特定的印表机。Windows程式不要求每个应用程式都自行开发这些驱动程式,因为这种支援是Windows的一部分。
动态连结
Windows运作机制的核心是一个称作「动态连结」的概念。Windows提供了应用程式丰富的可呼叫函式,大多数用於实作其使用者介面和在视讯显示器上显示文字和图形。这些函式采用动态连结程式库(Dynamic Linking Library,DLL)的方式撰写。这些动态连结程式库是些具有.DLL或者有时是.EXE副档名的档案,在Windows 98中通常位於\WINDOWS\SYSTEM子目录中,在Windows NT中通常位於 \WINNT\SYSTEM和\WINNT\SYSTEM32子目录中。
在早期,Windows的主要部分仅通过三个动态连结程式库实作。这代表了Windows的三个主要子系统,它们被称作Kernel、User和GDI。当子系统的数目在Windows最近版本中增多时,大多数典型的Windows程式产生的函式呼叫仍对应到这三个模组之一。Kernel(日前由16位元的KRNL386.EXE和32位元的KERNEL32.DLL实现)处理所有在传统上由作业系统核心处理的事务-记忆体管理、档案I/O和多工管理。User(由16位的USER.EXE和32位的USER32.DLL实作)指使用者介面,实作所有视窗运作机制。GDI(由16位的GDI.EXE和32位的GDI32.DLL实作)是一个图形装置介面,允许程式在萤幕和印表机上显示文字和图形。
Windows 98支援应用程式可使用的上千种函式呼叫。每个函数都有一个描述名称,例如CreateWindow。该函数(如您所猜想的)为程式建立新视窗。所有应用程式可以使用的Windows函式都在表头档案里预先宣告过。
在Windows程式中,使用Windows函式的方式通常与使用如strlen等C语言程式库函式的方式相同。主要的区别在於C语言程式库函式的机械码连结到您的程式码中,而Windows函式的程式码在您程式执行档外的DLL中。
当您执行Windows程式时,它通过一个称作「动态连结」的过程与Windows相接。一个Windows的 .EXE档案中有使用到的不同动态连结程式库的参考资料,所使用的函式即在那些动态连结程式库中。当Windows程式被载入到记忆体中时,程式中的呼叫被指向DLL函式的入口。如果该DLL不在记忆体中,就把它载入到记忆体中。
当您连结Windows程式以产生一个可执行档案时,您必须连结程式开发环境提供的特定「引用程式库(import library)」。这些引用程式库包含了动态连结程式库名称和所有Windows函式呼叫的引用资讯。连结程式使用该资讯在.EXE档案中建立一个表格,在载入程式时,Windows使用它将呼叫转换为Windows函式。
WINDOWS程式设计选项
为说明Windows程式设计的多种技术,本书提供了许多范例程式。这些程式使用C语言撰写并原原本本的使用Windows API来开发程式。我将这种方法称作「古典」Windows程式设计。这是我们在1985年为Windows 1.0写程式的方法,它今天仍是写作Windows程式的有效方法。
API和记忆体模式
对於程式写作者来说,作业系统是由本身的API定义的。API包含了所有应用程式能够使用的作业系统函式呼叫,同时包含了相关的资料型态和结构。在Windows中,API还意味著一个特殊的程式架构,我们将在每章的开头进行研究。
一般而言,Windows API自Windows 1.0以来一直保持一致,没什么重大改变。具有Windows 98程式写作经验的Windows程式写作者会对Windows 1.0程式的原始码感觉非常熟悉。API改变的一种方式是进行增强。Windows 1.0支援不到450个函式呼叫,现在已有了上千种函式呼叫。
Windows API和它的语法的最大变化来自於从16位元架构向32位元架构转化的过程中。Windows从版本1.0到版本3.1使用16位元Intel 8086、8088、和286微处理器上所谓的分段记忆体模式,由於相容性的原因,从386开始的32位元Intel微处理器也支援该模式。在这种模式下,微处理器暂存器的大小为16位元,因此C的int资料型态也是16位元宽。在分段记忆体模式下,记忆体位址由两个部分组成-一个16位元段(segment)指标和一个16位偏移量(offset)指标。从程式写作者的角度看,这非常凌乱并带来了long或far指标(包括段位址和偏移量位址)和short或near指标(包括带有假定段位址的偏移量位址)的区别。
从Windows NT和Windows 95开始,Windows支援使用Intel 386、486和Pentium处理器32位元模式下的32位元平坦定址记忆体模式。C语言的int资料型态也扩展为32位元的值。为32位元版本Windows编写的程式使用简单的平坦线性空间定址的32位元指标值。
用於16位元版本Windows的API(Windows 1.0到Windows 3.1)现在称作Win16。用於32位元版本Windows的API(Windows 95、Windows 98和所有版本的Windows NT)现在称作Win32。许多函式呼叫在从Win16到Win32的转变中保持相同,但有些需要增强。例如,图像座标点由Win16中的16位元值变为Win32中的32位元值。此外,某些Win16函式呼叫返回一个包含在32位元整数值中的二维座标点。这在Win32中不可能,因此增加的新函式呼叫以不同方式运作。
所有32位元版本的Windows都支援Win16 API(以确保和旧有应用程式相容)和Win32 API(以运行新应用程式)。非常有趣的是,Windows NT与Windows 95及Windows 98的工作方式不同。在Windows NT中,Win16函式呼叫通过一个转换层被转化为Win32函式呼叫,然後被作业系统处理。在Windows 95和Windows 98中,该操作正相反:Win32函式呼叫通过转换层转换为Win16函式呼叫,再由作业系统处理。
在同一时刻有两个不同的Windows API集(至少名称不同)。Win32s (「s」代表「subset(子集)」)是一个API,允许程式写作者编写在Windows 3.1上执行的32位元应用程式。该API仅支援已被Win16支援的32位元函式版本。此外,Windows 95 API一度被称作Win32c(「c」代表「compatibility(相容性)」),但该术语已被抛弃了。
现在,Windows NT和Windows 98都被认为能够支援Win32 API。然而,每个作业系统依然都支援某些不被别的作业系统支援的某些功能特性。因为它们的相同之处是相当可观的,所以有可能编写在两个作业系统下都可执行的程式。而且,人们普遍认为这两个产品最终会合而为一。
语言选项
使用C语言和原始的API不是编写Windows 98程式的唯一方法。然而,这种方法却提供给您最佳的性能、最强大的功能和在发掘Windows特性方面最大的灵活性。可执行档案相对较小且运行时不要求外部程式库(自然,Windows DLL自身除外)。最重要的是,不管您最终以什么方式开发Windows应用程式,熟悉API会使您对Windows内部有更深入的了解。
虽然我认为学习古典的Windows程式设计对任何Windows程式写作者都是重要的,我没有必要建议使用C和API编写每个Windows应用程式。许多程式写作者,特别是那些为公司内部开发程式或在家编写娱乐程式的程式写作者喜欢轻松的开发环境,例如Microsoft Visual Basic或者Borland Delphi(它结合了物件导向的Pascal版本)。这些环境使程式写作者将精力集中於应用程式的使用者介面和相关使用者介面物件的程式码上。要学习Visual Basic,您也许需要参考Microsoft Press的一些其他图书,例如Michael Halvorson1996年著的《Learn Visual Basic Now》。
在专业程式写作者中-特别是那些开发商业应用程式的程式写作者-Microsoft Visual C++和Microsoft Foundation Class Library(MFC)是近年来流行的选择。MFC在一组C++物件类别中封装了许多Windows程式设计中的琐碎细节。Jeff Prosise的《Programming Windows with MFC,第二版》(Microsoft Press,1999年)提供了MFC程式的写作指南。
最近,Internet和World Wide Web的流行大力推广著Sun Microsystems的Java,这是一个受C++启发却与微处理器无关的程式设计语言,而且结合了可在几个作业系统平台上执行的图形应用程式开发工具组。Microsoft Press有一本关於Microsoft J++(Microsoft的Java)开发工具的好书,《Programming Visual J++ 6.0》(1998年),由Stephen R. Davis著。
显然,很难说哪种方法更有利於开发Windows应用程式。更主要的是,也许是应用程式自身的特性决定了所使用的工具。不管您最後实际上使用什么工具写作程式,学习Windows API将使您更深入地了解Windows工作的方式。Windows是一个复杂的系统,在API上增加一个程式写作层并未减少它的复杂性,仅仅是掩盖了它,早晚您会碰到它。了解API会给您更好的补救机会。
在原始的Windows API之上的任何软体层都必定将您限制在全部功能的一个子集内。您也许发现,例如,使用Visual Basic编写应用程式非常理想,然而它不允许您做一个或两个很简单的基本工作。在这种情况下,您将不得不使用原始的API呼叫。API定义了作为Windows程式写作者所需的一切。没有什么方法比直接使用API更万能的了。
MFC尤其问题百出。虽然它大幅简化了某些工作(例如OLE),我却经常发现要让它们按我所想的去工作时,会在其他特性(例如Document/View架构)上碰壁。MFC还不是Windows程式设计者所追求的灵丹妙药,很少有人认为它是一个好的物件导向设计的模型。MFC程式写作者从他们使用的物件类别定义如何工作中受益颇深,并会发现他们经常参考MFC原始码,搞懂这些原始码是学习Windows API的好处之一。
程式开发环境
在本书中,假定您正使用Microsoft Visual C++ 6.0,标准版、专业版和企业版都可以。经济的标准版足以应付本书中的程式设计需求。Visual C++ 还是Visual Studio 6.0中的一部分。
Microsoft Visual C++ 套装软体中包括C编译器和其他编译及连结Windows程式所需的档案和工具等。它还包括Visual C++ Developer Studio,一个可编辑原始码、以交谈方式建立资源(如图示和对话方块)以及编辑、编译、执行和测试程式的环境。
如果您正使用Visual C++ 5.0,则需要为Windows 98和Windows NT 5.0更新表头档案和引用程式库,这些东西可从Microsoft的网站上得到。在
http://www.microsoft.com/msdn/ ,选择「 Downloads 」,然後选择「 Platform SDK 」(软体开发套件),您就能在选择的目录中下载和安装更新档案。要让Microsoft Developer Studio浏览这些目录,可以从「 Tool 」功能表项选择「 Options 」然後按下「 Directories 」标签。
Microsoft网站上的msdn部分代表「Microsoft Developer Network(Microsoft软体开发者网路)」。这是一个向程式写作者提供了经常更新的CD-ROM的计划,这些CD-ROM中包含了程式写作者在Windows开发中所需的最新东西。您也可以订阅MSDN,这样就避免经常得从Microsoft的网站下载档案。
API文件
本书不是Windows API权威的正式文件的替代品。那组文件不再以印刷形式出版,它仅能从CD-ROM或Internet上取得。
当您安装Visual C++ 6.0时,您将得到一个包括API文件的线上求助系统。您可通过订阅MSDN或使用Microsoft网站上的线上求助系统更新该文件。连接到 http://www.microsoft.com/msdn/ ,并选择「 MSDN Library Online 」。
在Visual C++ 6.0中,从「 Help 」功能表项选择「 Contents 」项目开启MSDN视窗。API文件按树形结构组织,寻找标有「 Platform SDK 」的部分,所有在本书中引用的文件都来自於该部分。我将向您介绍如何从「 Platform SDK 」开始寻找以斜线分层分门别类的文件的位置。(我知道「Platform SDK」是整个MSDN知识库中较为晦涩的部分,但我敢保证那是Windows程式设计的基本核心。)例如,对於如何在Windows程式中使用滑鼠的文件,您可参考/ Platform SDK / User Interface Services / User Input / Mouse Input。
我在前面提到Windows大致分为Kernel、User和GDI子系统。kernel介面在/ Platform SDK / Windows Base Services中,User介面函式在 / Platform SDK / User Interface Services中,GDI位於 / Platform SDK / Graphics and Multimedia Services / GDI中。
编写第一个WINDOWS程式
现在是开始写些程式的时候了。为了便於对比,让我们以一个非常短的Windows程式和一个简短的文字模式程式开始。这会帮助我们找到使用开发环境并感受建立和编译程式机制的正确方向。
文字模式(Character-Mode)模型
程式写作者们喜爱的一本书是《The C Programming Language》(Prentice Hall,1978年和1988年),由Brian W. Kernighan和Dennis M. Ritchie(亲切地称为K&R)编著。该书的第一章以一个显示「hello, world」的C语言程式开始。
这里是在《The C Programming Language》第一版第6页中出现的程式:
main () { printf ("hello, world\n") ; }
以前C程式写作者在使用printf等C执行期程式库函式时,无需先宣告它们。但这是90年代,我们愿意给编译器一个在我们的程式中标出错误的机会。这里是在K&R第二版中修正的程式:
#includemain () { printf ("hello, world\n") ; }
该程式仍然是那么短。但它可通过编译并执行得很好,但当今许多程式写作者更愿意清楚地说明main函式的返回值,在这种情况下ANSI C规定该函式必须返回一个值:
#includeint main () { printf ("hello, world\n") ; return 0 ; }
我们还可以包括main的参数,把程式弄得更长一些,但让我们暂且这样就好了-包括一个include宣告、程式的进入点、一个对执行期程式库函式的呼叫和一个return语句。
同样效果的Windows程式
Windows关於「hello, world」程式的等价程式有和文字模式版本完全相同的元件。它有一个include宣告、一个程式进入点、一个函式呼叫和一个return语句。下面便是该程式:
/*------------------------------------------------------------------ HelloMsg.c -- Displays "Hello, Windows 98!" in a message box (c) Charles Petzold, 1998 --------------------------------------------------------------------*/ #includeint WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0); return 0 ; }
在剖析该程式之前,让我们看一下在Visual C++ Developer Studio中建立新程式的方式。
首先,从 File 功能表中选 New 。在 New 对话方块中,单击 Projects 页面标签,选择 Win32 Application 。在 Location 栏中,选择一个子目录,在 Project Name 栏中,输入该专案的名称,此时该名称是 HelloMsg ,这便是在 Location 栏中显示的目录的子目录。 Create New Workspace 核取方块应该勾起来, Platforms 部分应该显示 Win32 ,选择 OK 。
将会出现一个标题为 Win32 Application - Step 1 Of 1 的对话方块,指出要建立一个 Empty Project ,并按下 Finish 按钮。
从 File 功能表中再次选择 New 。在 New 对话方块中,选择 Files 页面标签,选择 C++ Source File 。 Add To Project 核取方块应被选中,并应显示 HelloMsg 。在 File Name 栏中输入 HelloMsg.c ,选中 OK 。
现在您可输入上面所示的HELLOMSG.C档案,您也可以选择 Insert 功能表和 File As Text 选项从本书附带的CD-ROM上复制HELLOMSG.C的内容。
从结构上说,HELLOMSG.C与K&R的「hello,world」程式是相同的。表头档案STDIO.H已被WINDOWS.H所代替,进入点main被WinMain所代替,而且C语言执行时期程式库函式printf被Windows API函式MessageBox所代替。然而,在程式中有许多新东西,包括几个陌生的大写识别字。
让我们从头开始。
表头档案
HELLOMSG.C以一个前置处理器指示命令开始,实际上在每个用C编写的Windows程式的开头都可看到:
#include
WINDOWS.H是主要的含入档案,它包含了其他Windows表头档案,这些表头档案的某些也包含了其他表头档案。这些表头档案中最重要的和最基本的是:
这些表头档案定义了Windows的所有资料型态、函式呼叫、资料结构和常数识别字,它们是Windows文件中的一个重要部分。使用Visual C++ Developer Studio的 Edit 功能表中的 Find in Files 搜索这些表头档案非常方便。您还可以在Developer Studio中打开这些表头档案并直接阅读它们。
程式进入点
正如在C程式中的进入点是函数main一样,Windows程式的进入点是WinMain,总是像这样出现:
int WINAPI WinMain ( HINSTANCE hInstance,HINSTANCE hPrevInstance, PSTR szCmdLine,int iCmdShow)
该进入点在 / Platform SDK / User Interface Services / Windowing / Windows / Window Reference / Window Functions中有说明。它在WINBASE.H中宣告如下:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd );
您会注意到我在HELLOMSG.C中做了许多小改动。第三个参数在WINBASE.H中定义为LPSTR,我将它改为PSTR。这两种资料型态都定义在WINNT.H中,作为指向字串的指标。LP字首代表「长指标」,这是16位元Windows下的产物。
我还在WinMain宣告中改变了两个参数的名称。许多Windows程式中的变数名使用一种称作「匈牙利表示法」的命名系统,该系统在变数名称前面增加了表示变数资料型态的短字首,我将在第三章更详细地讨论这个概念。现在仅需记住字首i表示int、sz表示「以零结束的字串」。
WinMain函式宣告为返回一个int值。WINAPI识别字在WINDEF.H定义,语句如下:
#define WINAPI __stdcall
该语句指定了一个呼叫约定,包括如何生产机械码以在堆叠中放置函式呼叫的参数。许多Windows函式呼叫宣告为WINAPI。
WinMain的第一个参数被称作「执行实体代号」。在Windows程式设计中,代号仅是一个应用程式用来识别某些东西的数字。在这种情况下,该代号唯一地标识该程式,还需要它在其他Windows函式呼叫中作为参数。在Windows的早期版本中,当同时运行同一程式多次时,您便创建了该程式的「多个执行实体(multiple instances)」。同一应用程式的所有执行实体共用程式和唯读的记忆体(通常是例如功能表和对话方块模板的资源)。程式通过检查hPrevInstance参数就能够确定自身的其他执行实体是否正在运行。然後它可以略过一些繁杂的工作并从前面的执行实体将某些资料移到自己的资料区域。
在32位元Windows版本中,该概念已被抛弃。传给WinMain的第二个参数总是NULL(定义为0)。
WinMain的第三个参数是用於执行程式的命令列。某些Windows应用程式利用它在程式启动时将档案载入记忆体。WinMain的第四个参数指出程式最初显示的方式,可以是正常的或者是最大化地充满整个画面,或者是最小化显示在工作列中。我们将在第三章中介绍使用该参数的方法。
MessageBox函式
MessageBox函式用於显示短资讯。虽然,MessageBox显示的小视窗不具有什么功能,实际上它被认为是一个对话方块。
MessageBox的第一个参数通常是视窗代号,我们将在第三章介绍其含义。第二个参数是在讯息方块主体中显示的字串,第三个参数是出现在讯息方块标题列上的字串。在HELLMSG.C中,这些文字字串的每一个都被封装在一个TEXT巨集中。通常您不必将所有字串都封装在TEXT巨集中,但如果想将您的程式转换为Unicode字元集,这确是一个好主意。我将在第二章详细讨论该问题。
MessageBox的第四个参数可以是在WINUSER.H中定义的一组以字首MB_开始的常数的组合。您可从第一组中选择一个常数指出希望在对话方块中显示的按钮:
#define MB_OK 0x00000000L #define MB_OKCANCEL 0x00000001L #define MB_ABORTRETRYIGNORE 0x00000002L #define MB_YESNOCANCEL 0x00000003L #define MB_YESNO 0x00000004L #define MB_RETRYCANCEL 0x00000005L
如果在HELLOMSG中将第四个参数设置为0,则仅显示「 OK 」按钮。可以使用C语言的OR(|)操作符号将上面显示的一个常数与代表内定按钮的常数组合:
#define MB_DEFBUTTON1 0x00000000L #define MB_DEFBUTTON2 0x00000100L #define MB_DEFBUTTON3 0x00000200L #define MB_DEFBUTTON4 0x00000300L
还可以使用一个常数指出讯息方块中图示的外观:
#define MB_ICONHAND 0x00000010L #define MB_ICONQUESTION 0x00000020L #define MB_ICONEXCLAMATION 0x00000030L #define MB_ICONASTERISK 0x00000040L
这些图示中的某些有替代名称:
#define MB_ICONWARNING MB_ICONEXCLAMATION #define MB_ICONERROR MB_ICONHAND #define MB_ICONINFORMATION MB_ICONASTERISK #define MB_ICONSTOP MB_ICONHAND
虽然只有少数其他MB_常数,但您可以自己参考表头档案或 / Platform SDK / User Interface Services / Windowing / Dialog Boxes / Dialog Box Reference / Dialog Box Functions里的档案。
在本程式中,MessageBox返回数值1,但更严格地说它返回IDOK,IDOK在WINUSER.H中定义,等於1。根据在讯息方块中显示的其他按钮,MessageBox函式还可返回IDYES、IDNO、IDCANCEL、IDABORT、 IDRETRY或IDIGNORE。
这个小的Windows程式真的与K&R的「hello, world」程式有著同等效果吗?您也许认为不是,因为MessageBox函式并没有「hello, world」中printf函数所具有的潜在格式化文字能力。但我们将在下一章中看到编写类似printf的MessageBox版本的方法。
编译、连结和执行
当您准备编译HELLOMSG时,您可从「 Build 」功能表中选择「 Build Hellomsg.exe 」,或者按 F7 ,或者在「 Build 」工具列中选择「 Build 」图示。(该图示的外观显示在「 Build 」功能表中。如果当前没有显示「 Build 」工具列,您可从「 Tools 」功能表中选择「 Customize 」并选择「 Toolbars 」页面标签,选中「 Build 」或者「 Build MiniBar 」。)
另一种方法,您可从「 Build 」功能表中选择「 Execute Hellomsg.exe 」,或者按「 Ctrl+F5 」,或者在「 Build 」工具列单击「 Execute Program 」图示(该图示看上去像一个红的感叹号),就会弹出一个讯息方块询问是否编译该程式。
正常情况下,在编译阶段,编译器从C原始码档案产生一个.OBJ(目标)档案。在连结阶段,连结程式结合.OBJ档案和.LIB(库)档案以建立.EXE(可执行)档案。通过在「 Project 」页面标签上选择「 Settings 」并单击「 Link 」页面标签可以查看这些库档案的列表。特别地,您会注意到KERNEL32.LIB、USER32.LIB和GDI32.LIB。这些是三个主要Windows子系统的「引用程式库」。它们包含了动态连结程式库的名称以及放进.EXE档案的引用资讯。Windows使用该资讯处理程式对KERNEL32.DLL、USER32.DLL、GDI32.DLL动态连结程式库中函数的呼叫。
在Visual C++ Developer Studio中,您可用不同的设定编译和连结程式。内定情况下,它们是「Debug」和「Release」。可执行档案被存放在以这些名称命名的子目录下。在Debug设定下,资讯被附加到 .EXE档案中,这些资讯有助於测试程式和追踪原始码。
如果您喜欢在命令列下工作,附上的CD-ROM包含所有范例程式的.MAK(make)档案。(可通过「 Tools 」功能表选择「 Options 」,再选择「 Build 」页面标签,来告诉Developer Studio生成make档案。这里有一个核取方块需要勾选)。您需要执行在Developer Studio的BIN子目录下的VCVARS32.BAT来设置环境变数。要从命令列执行make档案,可以转到HELLOMSG目录并执行:
NMAKE /f HelloMsg.mak CFG="HelloMsg - Win32 Debug"
或者
NMAKE /f HelloMsg.mak CFG="HelloMsg - Win32 Release"
然後您可通过输入:
DEBUG\HELLOMSG
或者
RELEASE\HELLOMSG
从命令列执行.EXE档案。
我已经在本书附上的CD-ROM中对专案档案中的内定Debug设定做了一个改动。在「 Project Settings 」对话方块中,选择「 C/C++ 」页面标签後,在「 Preprocessor Definitions 」栏中,我已定义了识别字UNICODE。我将在下章中对此有更多的解释。