TONT 32723 当应用程序认为系统永远都不会变的时候:第3章



One of the stranger application compatibility puzzles was solved by a colleague of mine who was trying to figure out why a particular program couldn’t open the Printers Control Panel. Upon closer investigation, the reason became clear. The program launched the Control Panel, used FindWindow to locate the window, then accessed that window’s “File” menu and extracted the strings from that menu looking for an item that contained the word “Printer”. It then posted a WM_COMMAND message to the Control Panel window with the menu identifier it found, thereby simulating the user clicking on the “Printers” menu option.

我的一位同事曾解决过的一桩诡异的应用程序兼容性问题,是找出为什么某个程序无法打开打印机控制面板的原因。在深入调查后,原因变得明朗起来。这个程序会打开控制面板,用 FindWindow 找到控制面板的窗口,然后去访问这个窗口的 File(文件)菜单,把这个菜单里的菜单项字符串解析出来,然后在其中找到带有 Printer(打印机)这个词的项目,然后向控制面板窗口发送 WM_COMMAND 窗体消息,以刚才找到的菜单项为参数。经过以上一通操作,便完成了模拟用户单击『打印机』菜单项这个操作。(译注:可自行参考 Windows 3.x 的控制面板长什么样子,以便更好理解上文描述的操作)

With Windows 95’s Control Panel, this method fell apart pretty badly. There is no “Printers” option on the Control Panel’s File menu. It never occurred to the authors of the program that this was a possibility. (Mind you, it was a possibility even in Windows 3.1: If you were running a non-English version of Windows, the name of the Printers option will be something like “Skrivare” or “Drucker”. Not that it mattered, because the “File” menu will be called something like “Arkiv” or “Datei”! The developers of this program simply assumed that everyone in the world speaks English.)

到了 Windows 95 的控制面板里,这种操作可谓一败涂地。控制面板的『文件』菜单里并没有『打印机』这一项,对软件的编写者来说,这是根本不可能发生的事情。(需要提醒的是,即便在 Windows 3.1 中,这也是一件有可能发生的事:如果你运行的 Windows 不是英语版本,那么『打印机』一项的名字可能叫做 Skrivare(译注:瑞典语的『打印机』)或者 Drucker(译注:德语的『打印机』),但这其实也不是最大的问题,因为与此同时『文件』菜单的名字可是会叫做 Arkiv 或者 Datei(译注:分别为瑞典语和德语的『文件』)呢!编写这个软件的开发者估计简单的认为这个世界上的所有人都是讲英语的吧。)(译注:也可能人家根本就没想在英语区之外发行?)

The code never checked for errors; it plowed ahead on the assumption that everything was going according to plan. The code eventually completed its rounds and sent a garbage WM_COMMAND message to the Control Panel window, which was of course ignored since it didn’t match any of the valid commands on that window’s menu.

程序的代码从来都没有对错误进行过检查,只是一味地勇往直前,假定一切都会按计划进行而已。代码做完了它的工作,向控制面板窗口发送了一个无效的 WM_COMMAND 窗体消息,而这个消息由于没有包含针对窗体菜单的任何有效指令,理所当然地被系统忽略了。

The punch line is that the mechanism for opening the Printers Control Panel was rather clearly spelled out on the very first page of the “Control Panel” chapter of the Windows 3.1 SDK:

笑点在于,打开打印机控制面板的方式,实际上在 Windows 3.1 SDK『控制面板』章节的第一页就清清楚楚地写着:

The following example shows how an application can start Control Panel and the Printers application from the command line by using the WinExec function:

以下案例说明如何使应用程序通过调用 WinExec 函数来打开控制面板以及打印机设置界面:

WinExec(“control.exe printers”, SW_SHOWNORMAL);

In other words, they didn’t even read past the first page.


The solution: Create a “decoy” Control Panel window with the same class name as Windows 3.1, so that this program would find it. The purpose of these “decoys” is to draw the attention of the offending program, taking the brunt of the mistreatment and doing what they can to mimic the original behavior enough to keep that program happy. In this case, it waited patiently for the garbage WM_COMMAND message to arrive and dutifully launched the Printers Control Panel.

至于解决方案,则是创建了一个用作『诱饵』的控制面板窗口,这个窗口的类名与 Windows 3.1 的控制面板窗口一致,以便让这个程序能找到它。这个诱饵的作用是吸引这个无礼的应用程序的注意力,首当其冲地模仿旧式控制面板的行为,来让这类应用程序心满意足。在这个案例中,它的作用是耐心等待那个无效的 WM_COMMAND 窗体消息的来临,然后尽职尽责地打开『打印机』控制面板。

Nowadays, this sort of problem would probably have been solved with the use of a shim. But this was back in Windows 95, where application compatibility technology was still comparatively immature. All that was available at the time were application compatibility flags and hot-patching of binaries, wherein the values are modified as they are loaded into memory. Using hot-patching technology was reserved for only the most extreme compatibility cases, because getting permission from the vendor to patch their program was a comparatively lengthy legal process. Patching was considered a “last resort” compatibility mechanism not only for the legal machinery necessary to permit it, but also because patching a program fixes only the versions of the program the patch was developed to address. If the vendor shipped ten versions of a program, ten different patches would have to be developed. And if the vendor shipped another version after Windows 95 was delivered to duplication, that version would be broken when Windows 95 hit the shelves.

现如今,这类问题一般会通过使用『楔子』一类的方式解决,但这个案例是在 Windows 95 的年代发生的,那时应用程序兼容性技术相对来说还不够成熟,而那时能用的办法就是应用程序兼容性标签,以及对二进制文件应用热修复补丁,在其加载入内存时对二进制数据进行修改。热修复补丁这一招只会被留待遇到最棘手的兼容性问题时才会使用,因为从软件发行商哪里获得对其程序进行修改的许可,是一个冗长的法律过程。打补丁被视为是实现兼容性问题解决的『最终手段』,不仅仅因为获得许可要经过机械繁复的法律流程,同时也是因为这样只能修复兼容性补丁所针对的该软件特定版本的问题。如果软件发行商发布了10个版本的应用程序,那就要开发10个不同的补丁。而如果软件发行商在 Windows 95 送厂之后又发布了新版本,那么这个新版本在 Windows 95 上架销售之后便仍存在兼容性问题。

It is important to understand the distinction between what is a documented and supported feature and what is an implementation detail. Documented and supported features are contracts between Windows and your program. Windows will uphold its end of the contract for as long as that feature exists. Implementation details, on the other hand, are ephemeral; they can change at any time, be it at the next major operating system release, at the next service pack, even with the next security hotfix. If your program relies on implementation details, you’re contributing to the compatibility cruft that Windows carries around from release to release.

理解文档中受支持的功能和部署细节的区别很重要。文档中受支持的功能是 Windows 和你的程序之间的契约,Windows 在这个功能的存续期间会信守承诺。而另一方面,部署细节则是稍纵即逝的,可能随时发生变化,变化可能发生在下一个系统大版本,下一个 Service Pack,甚至是下一个安全更新补丁。如果你的程序依赖部署细节,那你不过是在为 Windows 代代相传的、沉重的兼容性包袱添砖加瓦罢了。

Over the next few days, I’ll talk about other decoys that have been used in Windows.

接下来的几天里,我会讲一讲 Windows 中其它的一些兼容性『诱饵』。


电子邮件地址不会被公开。 必填项已用*标注

 剩余字数 ( Characters available )

Your comment will be available after auditing.

Please DO NOT add any links in your comment, otherwise it would be identified as SPAM automatically and never be audited.