我的话:这只是很少的一部分,先贴出来。译得很粗略,不是套话,真的有很多错误!
作者笔记
为了更新有关这本书的信息,包括可能的错误报告和新的代码列表,请访问我们的网站www.ceptzold.com。你也可以把这本书中的问题投往chareles@cpetzold.com。虽然我将试着回答你问的每一个简单的问题,但我不能作出任何保证。我通常室非常的忙,而且我的猫拒绝学习windows API。
我要感谢微软出版社中为这本书的发行作其他伟大工作的每一个人。我想这个“十周年纪念版”是Programming Windows(windows编程)中最好的版本。微软公司中的其他人(包括一些微软windows的早期开发者)也在我写早期的版本时给了我很大的帮助。而且这些好人的名单被我列在了那些版本上。还要感谢我的家人和朋友,特别是那些最亲密的朋友(你知道你是谁!)。你们的支持使得这本书的出版成为可能。仅以此书献给你们。
Charles Petzold
1998年十月五日
第一章 入门
这本书向你展示怎样书写运行于微软Windows98,Windows NT4.0和Windows NT5.0的程序。这些程序是用C编程语言写的,并且使用了原始的Windows应用程序编程接口(APIs)。这就如我要在本章的后面讨论的一样,这不是写运行于Windows程序的唯一办法。然而,不管你最终用什么来写你的代码,理解Windows APIs都是重要的。
正如你可能知道的,Windows是使用32位Intel微处理器(如486和奔腾)的IBM兼容机个人电脑事实上的标准图形操作系统,而Windows98是它的最后一个特殊时期。Windows NT是运行于PC兼容机和一些RISCC精简指令集电脑工作站上,有工业水准的Windows版本。
使用这本书有三个先决条件。首先,你要从一个用户的角度熟悉Windows 98。你不能指望你在不了解它的用户界面的情况下为Windows写应用程序。因为这个原因,我建议你在一个使用Windows应用程序,基于Windows的机器上作你的程序开发工作(以及其他工作)。
第二,你应该知道C。如果你不懂C,Windows编程可能不是一个好的起点。我推荐你在像例如Windows 98提供的Ms-Dos命令行窗口的字符环境中学习C。Windows编程又是包括C在字符模式编程下不太显露的方面。在这种情况下,我将对他们作一些必要的讨论。但对于大部分时候,你应该对语言有一个工作上的熟悉,特别是C的结构和指针。一些标准C运行库的知识是有帮助的,但并不是必须的。
第三,你应当在你的机器上安装一个32位的C编译器和一个适合做Windows编程的开发环境。在这本书中,我假定你在使用微软Visual C++6.0。它可以单独购买,也可以作为Visual Stuido6.0的一部分一同购买。
这就是所有的,我不会假定你对图形界面(如Windows编程)有何经验。
Windows 环境
Windows几乎不用介绍。然而要忘记Windows给办公室何家庭桌面电脑带来的巨大变化却是容易的。Windows在它的早期,有一段崎岖的历程,而且几乎从来没有被认为注定要征服整个桌面市场。
Windows的历史
在IBM PC引入后的不久,1981年的春天,PC(和其兼容机)的优势操作系统是MS—Dos就变得显而易见。它原先代表微软磁盘操作系统。MS—Dos是一个小型的操作系统。对于用户,MS-Dos提供了一个用于下达诸如DIR或者TYPE以及把一个程序载入内存等命令的命令行。对于程序员,Ms-Dos提供了为数不多的用于文件输入输出(I/O)的功能命令集合。对于其他任务-特别是显示文本,有时是图象到视频现在设备-程序要直接控制Pc的硬件。
归咎于内存和硬件的限制,复杂的图形环境在小型电脑上很慢。苹果电脑提供了一个字符模式环境的替代品,当它在1983年一月发布了他多舛的Lisa。然后在1984年一月,用Macintosh为图形环境设立了标准。尽管Mac市场份额正在下滑,它仍然在衡量其他的图形环境时被认做标准。所有的图形环境,包括Macintosh和Windows,都要感谢Xerox Palo Alto研究中心(PARC)早在20世纪70年代中期所作的先锋式的工作。
Windows在1983年的11月由微软公司对外宣布(Lisa和Macintosh之后)而且在两年后,也就是1985你那的11月被正式发布。微软Windows1.0之后,紧接着有几个用来支持国际市场和提供更多的视频设备,打印机驱动程序的更新版本。
Windows2.0在1987年11月发布。这个版本包括几个用户界面上的改变。在这些改变中最有意义的是发展重叠窗口来替代Windows1.0中的平铺窗口。Windows2.0还包括键盘和鼠标接口上的改进,特别是菜单和对话框。
直到这个时候,Windows还仅仅需要Intel8086或者8088微处理器运行于“实模式”下,访问1M内存的支持。Windows/386(在Windows2.0之后不久发布的)使用Intel386微处理器的“虚拟86”模式和直接访问硬件的多个Dos程序的多任务。为了相称,Windows2.1被改名为windows/286。
Windows3.0在1990年5月22日被引入。先前的Windows/286和Windows/386在这个版本中被融合到了一起。在Windows3.0中最大的改变是对Intel286,386和486微处理器的16位保护模式的支持。这使得Windows和Windows应用程序可以访问多于16M的内存。Windows用于运行程序的“Shell(壳)”程序和文件的维护被彻底的改头换面。Windows3.0是第一个在家庭和办公室里立足的Windows版本。
说Windows的历史就不得不提到OS/2,一个Dos和Windows外的另一个选择,由微软和IBM合作开发。OS/2 1.0(仅有字符模式),运行在286(或更高)微处理器上而且在1987年被发布。在1988年10月OS/2 1.1有了图形呈现管理器(PM)。PM原先被认为是Windows的保护模式版本,但图形API的改动程度太大,以至于软件开发商很难同时支持两个平台。
到1990年9月,IBM和微软之间的冲突达到了高潮并且需要两个公司各走各的路。IBM采纳了OS/2而微软则明确的把Windows作为其操作系统战略的中心。当OS/2仍然有一些火热的追随者时,它仍然没有接近Windows的普及程度。
微软Windows版本3.1在1992年3月发布。几个重要的特色包括TrueType字体技术(它把无极缩放的字体带给了Windows),多媒体(声音和音乐),对象链接和插入(OLE)以及标准化的通用对话框。Windows3.1只能运行在保护模式下而且要求至少!MB内存的286或者386。
WindowsNT,在1993年7月被引入,是第一个支持Intel386,486和奔腾微处理器32位模式的Windows版本。运行在WindowsNT下的程序可以访问一个32位的平坦(flat)的寻址空间并使用32位指令集。(我将在后面对寻址空间讲更多)。WindowsNT也是被设计为可以移植到非Intel处理器的操作系统,并且已经运行于几种基于RISC的工作站上。
Windows95是在1995年的8月被引入的。同WindowsNT一样,Windows95也支持32位编程模式。虽然它缺少WindowsNT的一些特色,例如高安全性(译者注:天知道!)和对RISC机器的可移植性。但是Windows95有对硬件要求更低的优点。
Windows98在1998年6月发布,并且有大量的改进,包括外观,更好的硬件支持,和与互联网更好的融合。
Windows的方方面面
Windows98和WindowsNT都是32位抢先多任务和多线程的图形操作系统。Windows拥有一个图形用户界面(GUI),有时也被称为“可视化界面”或“图形Windows环境”,GUI背后的概念要追溯到20世纪70年代中期Xerox PARC为例如Alto和Star机器和像Smalk的环境所作的工作。这些工作成果晚些时候被带入主流并且由苹果电脑和微软公司传播开来。虽然有一小段时期的争论,但现在非常明显,GUI是(微软的Charles Simonyi说的)个人电脑工业最重要的“完美决意”。
所有的GUI利用显示在显示器上的图形。图形提供了对屏幕真实功能的更好利用,一个用于查询信息的可视的丰富环境,和WYSIWYG(所见机所得)的视频显示,和已经准备好用于打印的图形和文本。
在早期,视频显示设备只是用于响应用户用键盘输入的文本。在一个图形界面中,显示器本身成为用户输入的一个来源。显示器以图形的形式显示各种各样的图形化物体和输入方式例如按钮和滚动条。使用键盘(或者,更直接点,一个类似鼠标的指向设备),用户能够直接操作屏幕上的物体。图形物体可以被拖放,按钮可以按下,而滚动条可以滚动。
用户和程序间的相互作用因而变得更密切。不再是一条路的信息从键盘到程序到显示器的循环,用户直接操作显示器上的物体。
用户不再愿意花很长的时间去学用电脑或掌握一个新的程序。Windows帮了大忙,因为所有的应用程序有相同的基本外观和感觉。程序通常占用屏幕上一个矩形区域的窗口。所有的窗口都由标题条指示。大部分程序的功能是从程序的菜单调用。一个用户能够通过使用滚动条来查看因为太大而不能放在一个屏幕内的显示信息。一些菜单项打开对话框,在哪里用户得到附加的信息。一个对话框,特别的用来打开一个文件的对话框,几乎能够在每一个Windows大型程序中被发现。在所有的Windows程序中这些对话框看起来一个样子(或几乎一样),并且几乎总是从相同的菜单项选择启动。
一旦你知道怎样使用一个Windows程序,你就能很容易地学会其他的。菜单和对话框的使用使用户能够去试验一个新的程序并且发现它的特色。大部分Windows程序即可以用键盘,又可以用鼠标。虽然Windows程序的大部分功能可以通过键盘来控制,但是对于零星的事务来说,使用鼠标经常更容易。
从程序员的观点来看,一致的用户界面是使用Windows为菜单和对话框内建的建立方法的结果。因为是Windows而不是应用程序处理这些事务,所有的菜单有相同的键盘和鼠标操作方法。
为了便于用户操作程序,便于两者之间的交互,Windows支持多任务。几个Windows程序可以在同一时间运行和显示。每一个程序占用屏幕上的一个窗口。用户可以在屏幕上把窗口拖来拖去,改变它的大小,在不同的程序间切换,以及从一个程序向另一个程序传输数据。因为这些窗口看起来像桌面上的纸(当然是在桌面被电脑主宰之前),Windows有时说是使用“比喻义上的桌面”来显示多个程序。
早期的Windows版本使用一个叫作“无拖放”的多任务机制。这意味着Windows不用系统时钟在多个运行于系统的程序间把处理时间分片。程序本身不得不主动放弃控制权,来使得其他程序能够运行。在WindowsNT和Windows98下,多任务是抢先的并且程序本身可以切称多个看上去即时的线程。一个操作系统不能在不对内存作任何管理的情况下实现多任务。当新的程序被启动和旧的程序被终止的时候,内存可能变成碎片。系统必须能够重新整理释放内存空间。这需要系统把大片的代码和数据放入内存中。
甚至运行在8088微处理器的Windows1.0,也能够执行这种内存管理。受到实模式的约束,这种能力仅仅被看作令人震惊的软件工程技巧。在Windows1.0中没有要求任何多余内存的情况下,640千字节(KB)的Pc架构矛盾被很好的减缓了。但是微软并没有停步不前。Windows2.0给了Windows应用程序访问扩展内存(EMS)的能力,而且Windows3.0运行于保护模式时,给了Windows应用程序访问多于16MB扩展内存的能力。WindowsNT和Windows98通过平坦的内存空间实现完全成熟的操作系统,把这些旧的限制吹进了太平洋!
运行于Windows的程序共享位于叫做“动态连接库”的文件中的函数(routine)。Windows有一个图形界面。而且Windows程序可以充分利用显示器和打印机上的图形和格式化文本。一个图形界面不仅是在外观上更有吸引力而且可以分发高级的信息给用户。
Windows上的程序不直接访问诸如屏幕和打印机等图形硬件。而是Windows包括一个图形编程接口(叫做图形设备接口或GDI)。它使图形和格式化文本能够轻松的显示。Windows作实际上的显示工作。一个Windows上的程序可以在任何有Windows设备驱动程序的显示卡和打印机上运行。程序不需要管理哪种设备安装在了系统上。
把设备无关的图形界面放在IBM PC上,对于Windows的开发者来说,并不是一件轻松的工作。PC的设计是基于开放的结构。第三方的硬件商开发PC的外设是被支持的,而且已经有相当的数量。虽然出现了好几个标,但是陈旧的MS—Dos程序还是不得不个别的支持多种不同的硬件设备。为一个MS-Dos字处理程序销售带上一两张磁盘的小文件来支持各种打印机,是很平常的事情。
动态连接
Windows工作的中心是“动态连接”。Windows提供了应用程序可以使用其特性的丰富函数。大部分是用来实现它的用户界面和显示文本和图形到显示器的。这些函数在动态连接库,或说是在Dll文件中。这些文件有.Dll扩展名或者有时是.ExE,并且他们大部分位于\Windows\System(Windows98)和\Windows\System32(WindowsNT)子目录下。
在早期,Windows的那么多功能只是在三个动态连接库中实现。这代表了Windows的三个主要的子系统,它们是Kernel(核心),User(用户)和GDI(图形设备接口)。当子系统的树木在近期的Windows版本中增加时,一个典型的Windows程序作出的函数调用仍然是落在这三个模块中。Kernel(现在由16位的Krnl386.exe和32位的Kernel32.dll组成)处理一个操作系统传统上要处理的所有要素-内存管理,文件I/O(输入输出)和任务管理。User(实现在16位的User.exe和32位的User32.dlll中)指的是用户界面和所有的窗口逻辑。GDI(实现在16位的Gdi.exe和32位的Gdi32.dll中)是指图形设备接口,它使程序能够显示文本和图形到屏幕或者打印到打印机上。
Windows98支持了好几个应用程序要用到的函数。每一个函数有一个描述的名字。例如CreateWindows。这个函数(正如你可能猜到的)为你的程序创建一个窗口。所有的应用程序有可能用到的Windows函数在头文件中声明。在你的Windows程序中,你使用Windows函数的方法大体上和你使用诸如Strlen等C库函数一样。主要的区别在于C库函数的机器码被链接到你的程序中,但是Windows函数的代码在你程序之外的Dll中。
当你运行一个Windows程序时,它通过一个叫做“动态连接”的处理过程与Windows接口。一个Windows的.exe文件包括向多个它使用了其中函数的动态链接库的引用。当一个Windows称许被载入内存,程序中的api调用被解释为指向dll函数的入口点。如果它还未载入,那么也要载入内存。
当你链接一个Windows程序时产生可执行文件,你必须把由你的编程环境提供的“引入库”一起链接。这些引入库包含了动态链接库名和所有的Windows函数调用的参考信息。链接器使用这些信息来建立.exe文件中程序载入时Windows用来解释Windows函数调用的表(table)。
Windows编程的其他选择
为了说明Windows编程的各项技术,这本书有大量的例子。这些程序是用C写的,而且使用朴质的Windows APIs。我认为这种方法是经典的Windows编程。这就是我们在1985年Windows1.0时写程序的方法。这方法在今天,仍然是Windows编程的有效办法。
APIs和内存模块
对于一个程序员,一个操作系统是用它提供的API定义的。API包含了一个操作系统的应用程序可以作出的所有函数调用和相关类型和结构的定义。在Windows中,API还指一个我们将要在后面章节中探寻的特殊程序架构。
总的来说,Windows API自从Windows1.0以来就很稳定。一个有Windows98编程经验的程序员能够发现Windows1.0上的代码也很熟悉。API只是在性能的方面作出了改进。Windows1.0只支持不多于450个的函数,而现在有几千个。
WindowsAPI和它的语法的最大改变出现在16位架构向32位架构转型的时候。1.0版到3.1版,Windows使用16位的intel8086,8088和80286微处理器的一个叫做分段内存的模式。为了兼容,这种模式也被从386开始的32位intel微处理器支持。微处理器的寄存器在这种模式下的大小是16位的,因而C的int数据类型也是16位宽的。在分段内存模式下,内存地址是用一个16位的段指针和另一个16位的offset指针两部分组成。从程序员的观点来看,这非常的混乱而且长和远指针(即为段指针和offset指针)的区别和短与近指针(包括一个假定了段指针的offset指针)的区别。
从WindowsNt和Windows95开始,Windows使用intel386,486和奔腾微处理器的32位模式支持32位的平坦(flat)内存。C的int数据类型被发展为32位的值。为Windows的32版本写的程序只需要简单使用指示一个平坦的寻址空间的32位指针。
Windows16位版本(Windows1.0到Windows3.1)被称为win16。Windows32位版本(Windows95,Windows98和WindowsNT的所有版本)的API被称为Win32。许多win16的函数调用到Win32仍然是原样,但有的需要一些修改。例如,图形坐标点从win16中的16位值改成win32中的32位值。同时,一些win16函数返回一个32位值表示的二维的坐标点。这在win32中是不可能的,因而加进了不同方式工作的新函数。