尽管一直被吐槽,但是Windows依然是用得较多的个人操作系统。是什么原因让人对它吐槽不断,却又始终不离不弃?读完本书,相信你会有答案。从Windows 中‘开始(Start)’按钮的设计过程,到讨论只有极客们才会喜欢的GlobalAlloc,本书几乎涵盖了Windows 系统从高层到低层的方方面面,既有引人入胜的小八卦,也有深入的技术分析,它将帮助你真正地体会在设计和编写高质量软件产品的过程中存在的困难和不易。
凭借在微软Windows 开发团队十多年的工作经验,作者Raymond Chen 将向你揭示你不可不知的Windows 系统内幕。《伟大的产品:Windows进化启示录》的许多内容都是讲述某项技术的来龙去脉,通过了解这些历史故事,你不仅可以更加透彻地理解Windows 的设计思想,还可以澄清一些由来已久的误解。
对于Windows 平台的开发者,阅读《伟大的产品:Windows进化启示录》有助于提高工作效率;而对于软件产品经理、UI 设计人员,则能从其中获得不少有益的启示。
推荐序
我记得在 2007年前后曾经阅读过“The New Old Thing”这本书,给我留下深刻印象的是,这本书介绍了 Windows开发团队中经历过的一些趣事。未必是高深的理论,却揭示了我们每天使用的 Windows是如何长成这个样子的,一些编程套路是如何形成的。
Windows操作系统可算得上是人类复杂工程的一次成功实践,一套代码服务于几亿规模的来自于不同硬件厂商的 PC机器,而且持续进化了二十多年。这其中的经验和教训何其宝贵,本书展示的正是作者亲身经历过的各种编程实践活动,涵盖了用户体验设计、API进化,以及系统层面的设计,甚至还涉及了 Windows团队的工在一些技术书籍中程师文化。
多年以后再来翻阅这本书,怀旧的感觉非常强烈。最近 5年,我已经不再使用 Windows开发平台了,但仍然坚持使用 Windows作为自己的办公系统。偶尔在公共场所看到 Windows出丑的界面,比如广场大屏出现蓝屏、地铁闸口出现 Windows 2000风格的出错对话框、自行车租赁处出现 Windows启动界面,瞬时感觉无比亲切。在信息技术飞速发展的过去二十多年,Windows影响着大多数技术人员,本书中讲述的各种技术和非技术的点点滴滴,是大家曾经共同面临或感受过的。
今天,随着智能设备的快速普及,智能操作系统获得了软件开发人员的广泛关注,Windows已经风光不再。但是在 Windows平台上的开发经历却仍然是许多开发人员的共同话题,本书中绝大多数章节的内容依然是技术人员的共同语言。我不止一次见到过这样的场景,Android或者 iOS的开发人员在向他们的老板描述一个技术问题时,用 Windows平台上的对应术语或做法进行解释。在这样的场景中,管理者的 Windows背景被强化了,年轻一代更易于接受新的技术和平台;另一方面,也说明了 Windows平台虽然不再像以往一样被广泛使用,但它倡导的编程模式和技术框架依然被沿袭到各种后来的开发平台,甚至嵌入式系统或移动智能操作系统中。
这不是一本教你如何进行 Windows编程的书,也不是讲解 Windows的核心技术的书,但是,阅读本书会有意想不到的收获。你可以理解 Windows背后的一些设计来源,以及 Windows进化过程中的一些有趣故事。如果你有过 Windows平台的开发经历,那么,很多章节的内容一定可以引起你的共鸣,并且更好地理解 Windows之所以成为我们看到的 Windows。
Windows NT是一个具有强设计的操作系统,在二十多年的进化过程中,其初始的设计始终主导着它的每一个版本。但是,在一些细微或局部的方面,也会随着版本的进化而发生变化。这种变化可能是做了妥协,也可能是做了优化,甚至是一些意想不到的原因导致,但通常可让系统更加满足实际的需求。通过本书,我们可以轻松地了解到 Windows曾经经历过的各种设计选择和变化,也可以让我们在面对其他系统的时候有更深刻的理解。
潘爱民
2015年 12月于杭州城西
再版译序
光阴似箭,岁月如梭。自 2007年首次出版本书以来,已经过近十年时间。这段时间是移动互联网发展的黄金时代,然而微软却频频错失发展机遇,使得 Windows的日渐式微与 iOS与 Android的强势崛起形成了鲜明对比。2015年 Windows 10的发布,或许是微软过去数年以来做出的最为正确的战略部署,有望借助覆盖所有尺寸设备的大一统平台,重回其昔日的巅峰地位。
尽管 Windows在移动领域处于弱势,但其在传统桌面领域的统治性地位仍然难以撼动,这与其优秀的设计理念是分不开的。尽管每天都有大量的开发人员在 Windows平台上工作,然而许多人对 Windows蕴含的设计思想却了解有限,对于一些重要功能也只是知其然,而不知其所以然,因为最熟悉的东西往往也是最容易被忽视的。例如,在看似简单的“开始”按钮背后,就有着复杂的设计考虑。在 Windows平台上开发应用时,如果不了解这些知识,就很难像 Windows那样将用户体验设计做到极致。
本书的内容在 2007版的译序中已做了简要介绍,这里不再赘述。此次再版翻译的主要工作包括,对译稿的文字做出了一些调整,使得阅读起来更为流畅,并对其中的一些错误进行了更正。
记得 2007年初次翻译本书时,译者还是一名普通的 Windows程序员。后来,由于工作需要,先后在 Linux、Android等平台上从事过开发,涉及的领域包括大规模存储系统、移动终端浏览器引擎及 GPU并行加速等,而目前又重新回到Windows平台从事三维设计系统的开发。在十余年的职业生涯中,较为深刻的感悟之一就是“大道至简”。像 Windows这样的软件系统可谓博大精深,但其中的基本原理、方法和规律却往往是极其简单的,本书也正是向读者阐述 Windows中的这些道理,希望读者们能够从中有所收获。
致谢
首先要感谢电子工业出版社的编辑,使本书能够有再版的机会。感谢妻子云兰和我们的两个孩子彤彤、越越,是你们让我充满了工作的动力。
聂雪军
2016年 1月于湖北海洋工程装备研究院智能技术研究所
译序
对于有经验的 Windows程序员来说,每天调用各种各样的 Windows API早已成为了一种习惯,甚至无须参考 MSDN也能够说出每个函数的用法和参数的含义。可是,你知不知道为什么这些函数要设计成这样的工作方式?知不知道有些常见的函数在多线程与单线程之间存在着一些微妙的差异?或许有些细节你根本就没有注意到,只有当有人指出来之后才恍然大悟。作为最成功的软件之一,在 Windows中自然有许多设计思想和基本理论是值得学习的,而这也正是本书的重点所在。在阅读完本书之后,相信读者的编程水平能够上升到更高的境界。
本书所讲述的内容涉及 Windows的各个方面。从用户界面行为到内核工作机制,从最初的 Windows 1.0到后来的 Windows Vista,可以算是一部简单的 Windows“发展史”。本书的许多内容都是讲述某项技术的来龙去脉,通过了解这些历史故事,你不仅可以更加透彻地理解 Windows的设计思想,还可以澄清一些由来已久的误解,这将有助于你在开发过程中更加得心应手,并极大地提高编程效率。此外,你还能够从这些故事中得到 Windows在发展过程中的一些经验教训,以此为鉴,这将有助于你在今后的程序开发中避免重复以前的错误。
在本书中还穿插了一些在 Windows开发中的趣事,作者 Raymond Chen以一种轻松幽默的语气来叙述这些故事。正如书中所指出的,编程工作并不总是严肃的和枯燥的,它只是众多工作中的一种,自然也有着其独特的乐趣。在翻译本书的时候,妻子云兰(对 Windows的认识仅限于普通的操作)曾自告奋勇地成为本书的第一个读者,她常常为这些趣事感到开心不已。
因此,本书适合不同层次的读者,从专业的开发人员到普通的 Windows用户,都可以从中获益。对于专业开发人员来说,可以从本书中收获深层次的设计思想。对于普通的 Windows用户来说,则可以把本书的一部分内容当作有趣的故事来阅读。
在本书的翻译过程中,译者总是尽最大努力将每一段内容都明白无误地呈现给读者。然而,由于译者的水平和时间有限,翻译中的疏漏和错误在所难免,还望读者和同行不吝指正。
致谢
首先要感谢华章公司的冀康对于我的信任和耐心,使得我能够顺利地完成本书。感谢妻子云兰和女儿彤彤,你们给我增添了许多的乐趣,使我很快地忘却工作的疲惫。感谢我的父母,你们一直都在默默地支持着我。
聂雪军
2007年 5月于武汉
第 1 章 用户界面设计初探
为什么要单击“开始(Start)”按钮来关机
为什么Windows 没有“专家模式(expert mode)”
对话框的默认按钮是“取消”
最好的设置是:即使你没有意识到这些设置的存在,但它们依然按照你所期望的
方式在工作
为了显示我们的超群智慧,现在就来问一个你回答不了的问题
为什么安装程序不会问你是否希望保留操作系统文件的新版本
功能设计的思考
什么时候应该禁止选项,而什么时候又该删除选项
什么时候应该将“…”放在按钮或者菜单项的后面
自动售货机的用户界面设计
室内门锁的用户界面设计
Windows 用户界面中“睫毛膏”的演变
第 2章 Windows 95 的精选回忆录
为什么在世界地图中,当前时区没有被加亮显示
为什么当内存超过 1GB 时,Windows 95 无法启动
为什么在Windows 95 中有些函数叫作BEAR、 BUNNY 和PIGLET
BOZOSLIVEHERE 和TABTHETEXTTOUTFORWIMPS 表示什么含义
在Windows 95 特别版的包装盒中都有些什么东西
Windows 95 引出了每个人的罗尔沙赫氏测试
登录时的武术图片
为什么一个非常大的词典反而不好
了解Windows 95 的启动声音
如果不在意正确性,写专栏文章是很容易的
为什么在系统属性页中对内存的大小进行了取整
为什么硬盘指示灯每隔几秒钟闪一下
寻求更快的系统陷阱
一个字节曾经价值一美元
每个产品支持电话的成本相当于卖出一个Windows 拷贝
为什么在Windows 的光盘中没有包含Tweak UI
不能通过xcopy 来安装Windows
买下一个Egghead 软件商店
Windows PowerToys 的历史故事
Windows 如何选择最终构建编号
为什么在安装系统补丁包时系统的构建编号不会增加
第 3 章 GetWindowText 函数的秘密
窗口如何管理文本
深入了解GetWindowText 函数
如果我不喜欢这些规则,那该怎么办
能否给出一个示例程序来说明这种差异
为什么GetWindowText 的规则如此奇怪
第 4 章 任务栏与通知区域
为什么有些人把任务栏叫作“托盘”
为什么任务栏默认是在屏幕的底部
为什么在任务栏中的时钟并不显示秒
为什么不在任务栏中显示模拟时钟
为什么当任务栏竖直停靠时,“开始”按钮上的文本消失了
为什么当用户单击“X”按钮,通知图标不会收到消息
第 5 章 令人困惑的界面问题
那些小小的覆盖图标是什么
为什么当我登录时,有些不希望看到的文件/文件夹会自动打开
对文件来说,文件名字体的颜色代表什么含义
为什么在高级选项对话框中,在每个选项后面都会加上“开”或者“关”
Alt+Tab 中的图标顺序是如何确定的
为什么文件夹的“只读”属性非常奇怪
当我单击空白任务栏按钮时,这些按钮消失了,发生了什么事
“最小化所有窗口”和“显示桌面”之间的区别是什么
在菜单中的粗体文本表示什么含义
自定义的网页图标是从何而来的
任务管理器的标签和按钮到哪里去了
拖动一个文件是表示移动还是复制
为什么“链接”文件夹总是不断地自我创建
为什么同时选择多个文档进行打印时,文档的打印顺序是乱的
我在产品支持部门的一天
吹掉连接器上的灰尘
1G 字节到底有多少
为什么不能删除“仅用于测试/评估”的标记
第 6 章 GlobalAlloc 函数的历史
从前的故事
选择符
过渡到Win 32
实现示例
第 7 章 Windows 编程中的一些话题
“临时”程序
获得在标题图标中的自定义右键菜单
CreateMenu 与CreatePopupMenu 有什么区别
为什么窗口管理器会自动销毁菜单
仅当窗口在屏幕上可见时才进行绘制
判断窗口是否被覆盖了
用位图刷来实现平铺效果
DC 画刷的好处是什么
用ExtTextOut 函数来绘制实心矩形
用StretchBlt 函数来绘制实心矩形
在显示字符串时去掉那些难看的方框
没有所有者的信号量
自动复位的事件只是一个毫无意义的信号量
第8 章 窗口管理
为什么会收到伪WM_MOUSEMOVE 消息
为什么没有WM_MOUSEENTER 消息
白屏
空心画刷的作用是什么
桌面窗口有什么特殊的地方
禁止窗口和激活窗口的正确顺序
恢复窗口位置时的问题
界面模态与代码模态
WM_QUIT 消息与模态
为模态界面设置正确的所有者窗口
与进入模态的程序进行交互
定时消息框的简单版本
临时窗口
在GWLP_USERDATA 中的附加窗口数据
定时消息框的改进版本
定时右键菜单
为什么窗口在被销毁之后还会收到消息
第 9 章 关于硬件的回忆录
硬件的向后兼容性
光驱
微软公司的网络:比“地狱”差 1.7 倍
有时候制造商会自取其辱
欺骗WHQL 的驱动程序认证过程
20 英尺长的计算机
USB 手推车
检测到了新设备:波音 747
超频带来的问题
第 10 章 对话框管理器的内部工作机制
关于对话框过程
对话框模板的发展
为什么需要对话框模板
对话框是如何创建的
模态对话框的消息循环
嵌套对话框以及DS_CONTROL
为什么需要对话框循环
为什么对话框编辑器从 100 开始设置控件的ID
在DefDlgProc 函数中做了哪些工作
不要将焦点设置在被禁止的控件上
在IsDialogMessage 中做了什么工作
为什么有些消息框中的“X”按钮是被禁止的
第 章 常见的软件问题
为什么夏令时与我们的直觉不同
为什么当文件复制到软盘时,文件的时间戳会发生变化
不要相信返回地址
编写排序比较函数
可以从另一端来理解契约
实用主义和纯粹主义之间的争论
优化通常是违背直觉的
在服务器上,分页=死机
不要保存任何能够重新计算的结果
通过增加其他组件的开销来提升性能
轮循的性能问题
检测内存泄漏的简单方法
不好的缓存策略将导致内存泄漏
第 12 章 深入研究Visual C++编译器
析构函数在什么时候调用
COM 对象的布局
调节转换器
指向成员函数的指针是非常奇怪的
什么是__purecall
第 13 章 向后兼容性
有些应用程序的本意就是要造成崩溃
当程序使用未公开的结构时
为什么不阻止那些使用了未公开结构的程序
为什么 16 位DOS 和 16 位Windows 仍然存在
像NUL 和CON 这些保留文件名的作用是什么
为什么(有时候)在UNC 路径前面是一个驱动器盘符
不要轻视“猎鹿者”这个游戏的威力
有时候,游戏中的bug 只有在玩了一段时间后才会显现出来
Shell Folders 键的故事
保持错误码的向后兼容性是很重要的
没错,我们实现了这个功能
有些程序在为操作系统打补丁时将陷入困境
即使在内部数据结构中也存在着兼容性问题
为什么Windows 让BIOS 时间保持为本地时间
版本号的检测
破坏IUnknown::QueryInterface 的几种方式
当程序假设操作系统永远不会发生改变――之一
当程序假设操作系统永远不会发生改变――之二
伪显示控制面板
伪可视化风格
第 14 章 一些名字的来源和历史
在WPARAM 和LPARAM 中,字母W 和L 分别表示什么意思
为什么在Windows 98 中,显示器的最大数量是 9
为什么注册表文件被叫作蜂窝
16 位Windows 中对资源的内存管理
HINSTANCE 和HMODULE 之间的区别是什么
在WinMain 函数中,hPrevInstance 参数的作用是什么
为什么GlobalWire 函数被叫作这个名字
LocalAlloc 和GlobalAlloc 之间的区别是什么
GMEM_SHARE 标志的作用是什么
为什么在转换到LPARAM 之前会进行一个多余的转换
为什么有些注册表函数的名字以Ex 结尾
SHGetMalloc,SHAlloc 和CoGetMalloc 之间的区别是什么
为什么Windows 错误报告程序的昵称是DrWatson
DirectX 4 出了什么问题
为什么HANDLE 类型的返回值如此不一致
为什么文本文件是以Ctrl + Z 来结束的
为什么行结束符是CR + LF
TEXT、__TEXT 与__T 和UNICODE 与__UNICODE
为什么对话框在初始创建时是隐藏的
当程序在内部做修改时,没有人会注意
如果FlushInstructionCache 没有做任何事情,为什么还要调用这个函数
如果InitCommonControls 没有做任何事情,为什么还要调用这个函数
为什么InterlockedIncrement/InterlockedDecrement 这两个函数只是返回结果的
符号
为什么会存在WSASetLastError 函数
为什么在Windows 中使用了基于广播的机制
在任务栏出现之前,窗口最小化之后被放到了什么地方
为什么在计算桌面窗口大小时会把任务栏也包含在内
为什么在按下Alt 键时,光标将停止闪烁
ES_OEMCONVERT 风格的作用是什么
在文件系统隧道背后隐藏的故事
为什么NTFS 和资源管理器在对文件名进行排序时是不一致的
日期/时间控制面板并不是日历
Windows 如何重新设定DLL 的加载基址
SYSTEM_FONT 和DEFAULT_GUI_FONT 是什么字体
为什么上下控件中的箭头是相反的
Windows 95 发布会的门票
第 15 章 窗口消息的发送与接收
发送消息和投递消息
发送消息的生命期
投递消息的生命期
生成的投递消息
SendMessageCallback 将在什么时候调用回调函数
当消息超时后,SendMessageTimeout 函数将执行什么操作
澄清一些关于消息处理的谬论
如何知道消息的发送者/投递者
不能用PostMessage 来模拟键盘输入
第 16 章 国际化编程
在Unicode 上做大小写映射是很困难的
关于错误大小写映射的趣事
为什么不能旋转文字
0409 和 1033是些什么
注意编码页
为什么默认的 8比特编码页叫作“ANSI”
为什么默认的控制台编码页叫作“OEM”
为什么OEM 编码页经常被叫作ANSI
在Unicode 和ANSI 之间的转换结果既是合理的但也是奇怪的
第 17章 安全
所有用户都可以写入的文件
在资源管理器中隐藏文件
窃取密码
未验证驱动程序的静默安装
调试代码可能是一个安全漏洞
为什么共享数据段是一个安全漏洞
IE 的增强安全配置并不信任内部网络
第 18章 Windows 2000 和Windows XP
为什么在Windows XP“开始”菜单的“所有程序”列表中没有智能菜单
为什么没有定义可以访问“开始”菜单中快速启动列表的函数接口
为什么Windows XP Service Pack 2 有时候会忘记CD 自动播放设置
不安全设备删除对话框
关于Windows XP 中“Comments”按钮的两段回忆
为什么资源管理器在刻录完光盘之后会把光盘弹出来
为什么Windows 安装程序会生成新的启动扇区
超自然力调试法:为什么在四处理器的机器中有三个处理器没有发挥作用
超自然力调试法:为什么CPU 使用率总在 50%上下徘徊
DS_SHELLFONT 标志的作用是什么
为什么DS_SHELLFONT = DS_FIXEDSYS | DS_SETFONT?
DS_SHELLFONT 在属性页上的效果是什么
第 19章 Win 32中的设计问题
为什么当无法解析某个导入函数时,Win 32就不会加载这个模块
为什么要仔细检查结构的大小
为什么必须为了WM_DEVICECHANGE 返回一个奇怪的值?
程序和用户之间的战争
为什么不能截获TerminateProcess 函数调用
为什么有些进程在被终止之后还停留在任务管理器中?
理解WAIT_ABANDONED 所带来的结果
为什么不能把超链接放在通知图标气球提示中
为什么在树型控件中不能重复使用同一个节点
奇怪的STRRET 结构
为什么不能把UTF-8 设置为系统的ANSI 编码页
什么时候应该使用下沉的客户区
为什么没有包罗万象的Windows 版本
为什么可能发生禁止桌面窗口的情况
窗口和菜单的嵌套限制分别是多少
HWND_TOP 和HWND_TOPMOST 之间的区别是什么
第 20章 税赋
分级存储管理
地缘政治学
远程桌面连接与绘制
快速用户切换和终端服务
多用户
漫游用户配置文件
重定向文件夹
“我的文档”与“应用程序数据”
大地址空间
电源管理和检测电池的电量
间歇性的网络连接
反走样字体和ClearType 技术
高DPI 显示
多显示器
工作区
在正确的位置上显示弹出窗口
辅助功能
第 21章 一些可笑的故事
容易误解的“空”操作
不要让市场部门搞砸你的幻灯片
异想天开的Bug 报告
小心示例URL
任何代码都不是孤立的
我在Viusal Basic 方面很专业
半透明的塑料
我遭遇的第一次死亡威胁
你无法摆脱这些AOL CD
在接入电脑的电源之前发出严正警告
蜘蛛纸牌不再排名第一
关于Rat Poker 的一些事情
为产品小组取名时请三思
讨论组的命名心理学
经理和程序员的区别
将软盘作为信号量
当一个标志中途改变了它代表的含义
把异想天开的尴尬作为一种温和的指责方式
用物品来提醒
办公室里的迪士科舞会
万圣节主题的大厅
“Raymond Chen是一个擅长讲述 Windows故事的人。”
——Scott Hanselman,ComputerZen.com
“Raymond在微软工作了多年,他见到过许多关于 Windows的趣事,而其他人可能只是略知一二。在这本书中,Raymond将与你一起分享他的知识、经历,以及 Windows的一些奇闻轶事,本书将帮助我们更好地理解这个每天影响着成千上万人的操作系统。每个人都能从本书中得到他们想要的东西,这本书读起来很轻松,非常值得推荐。”
——Jeffrey Richter,Wintellect公司创始人之一,作家兼顾问
“这是一本非常有趣的书, Raymond将告诉你一些关于 Windows的内幕故事。”
——Eric Gunnerson,微软公司程序经理
“如果你想了解 Windows的历史、奇闻轶事以及它们的来龙去脉,那么这绝对是一本值得推荐的书。”
——Matt Pietrek,MSDN杂志 Under the Hood专栏作家
“Raymond Chen已经成为了软件界的传奇人物,在本书中你将找到他为什么能够达到这种高度的原因。从回忆 Windows‘开始(Start)’按钮的设计过程,到讨论只有极客们才会喜欢的 GlobalAlloc,几乎涵盖了 Windows系统从高层到低层的方方面面,可以说这是一本引人入胜的奇闻轶事集,它将帮助你真正地体会到在设计和编写高质量软件的过程中存在的困难。”
——Stephen Toub,MSDN杂志技术编辑