组件检查
我们发现导致无法在 Windows 2000 上安装应用程序的另一个原因是组件检查功能。显然,我们操作系统的每一版本都是由多个不同的组件组成。这些组件包括 TAPI、MAPI、Microsoft DirectX(R) 等等。我们发现应用程序会对什么组件处于什么位置,以及是否存在某一组件作出自己的假设。应用程序会因为甲组件存在而假定乙组件也存在,因为某一组件的版本 2 存在而认定另一组件的版本 3 也必然存在。如果您需要用到某一组件,则一定要检查系统中是否存在这一组件,以及它是否位于正确的级别。
除此之外,开发人员还会假设 Windows NT 中没有 DirectX。有时候某一语句为真,而且应用程序在编制时也认为它可能为真。然后在对其进行检查时,因假设 Windows 2000 没有 DirectX,它就会声明:“噢,我无法运行。”但是 Windows 2000 带有 DirectX,这一假设是不正确的。
我们遇到的另一问题是硬编码问题,应用程序如果假定组件所处的位置是错误的,则根本无法对路径进行硬编码。
另一个例子是,Windows 2000 现在包含 TAPI(最新版本为 TAPI 3.0)和 DirectX(最新版本为 7),而不是默认情况下的 MAPI。过去总是认为如果操作系统是 Windows NT,其中应该包含 MAPI。但现在的情况不同了。如果您使用某一组件,则一定要检查这一组件是否存在,而不能根据平台或组件等作出任何假设。
在错误的位置安装文件
我们在安装方面所发现的另一问题是人们放置文件的位置出错。如果您对别人置于某处的某些文件进行升级,那没关系;只要将它们置于用户所希望的位置即可。但有些文件在第一次安装时,需要将其置于“Program Files”目录下。
注意 不一定是 C:\Program Files。有时候是这一目录,有时候则不是。比如在我的机器上就不是。我一般都将 C 分区留给 Windows 98,而将 Windows NT 分区安装到其他分区。
我们希望您尽可能不要将文件置于 Windows 目录,或 System32 等任一子目录。虽然这样做本身没有什么坏处,过去我们也常常希望您如此,因此我们无法完全禁止这一做法,但我们现在希望系统中的所有文件更加有组织一些。我们已经发现,用户对 System32 目录下成百上千的文件深感头痛,对这些文件一无所知,因此也无法删除他们。您每每听到人们这样说:“每年,您都要清理系统,并从头开始重装 Windows”。卸载和清理程序已成为软件市场上最流行的应用程序。我们希望系统保持有序状态,并使操作过程更加简化。
可能的话,我们甚至希望您将某些共享组件也写到别处;Microsoft Visual Basic(R) 在这方面就是一个比较好的例子。许多应用程序都会用到这一组件。旧版 Visual Basic 总是安装在系统目录中,而现在我们将它置于 Program Files 的一个指定文件夹内。
如果要查找文件应该放置的位置,可以调用一个名为 SHGetFolderPath 。SHGetFolderPath 是 Windows 2000 的一个组成部分。它在第二版的 Windows 98 中也已经存在。如果您发现系统中没有这一 API,您可以对S HFolder.dll 进行分发。该 DLL 将解开这一名为 SHGetFolderPath 的新 API 的外壳。这一 API 了解每一特殊文件夹在系统中所处的位置。您可以在 SDK 中找到该 API。它是一个非常长的清单,其中列出了您能够考虑选用的每一个文件夹。
需要注意,旧版的平台只支持以下四个 CSLID。如果您要查找某一特定目录,发现该目录不存在,SHGetFolderPath 能够为您创建该目录,条件是您已指定了这一操作,但在 Windows 95 等后向平台中,这一过程只能对以下四个 CSIDL 有效:
CSIDL_PERSONAL
CSIDL_APPLICATIONDATA
CSIDL_MYPICTURES
CSIDL_LOCAL_APPLICATIONDATA
以下代码示例显示了 SHGetFolderPath 的应用方式;它是一个非常简单易用的 API。有一点需要注意:如果希望代码在所有平台上都能运行(包括 Windows 2000 等自带 SHGetFolderPath 的平台和 Windows 95 等不带 SHGetFolderPath 的平台),应用程序必须与 SHFolder.dll 中的实施过程进行动态链接。
// SHGetFolderPath can work everywhere SHFOLDER is installed.
HMODULE hModSHFolder = LoadLibrary("shfolder.dll");
if ( hModSHFolder != NULL )
{
(*(FARPROC*)&g_pfnSHGetFolderPath =
GetProcAddress(hModSHFolder,"SHGetFolderPathA"));
}
else
g_pfnSHGetFolderPath = NULL;
}
if (g_pfnSHGetFolderPath != NULL )
g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);
else
szSystem32[0] = '\0';
OpenFileName.lpstrInitialDir = szSystem32;
安全性问题
最后,作为设置和安装的最后一个问题,我们将讨论随 Windows 2000 出现的几个安全性问题:
高级用户应能安装整个系统范围的应用程序。我们发现有些应用程序坚持只能由管理员进行这类安装。对于锁定的机器,企业仍然希望许多不同用户,或一天中有两三个用户能够进行共享;或者是在特定的某一天,所有雇员都能够使用这一系统。他们只是不希望任何人都能以管理员权限对系统任意更改,为所欲为。许多客户要求高级用户(不仅是管理员)也能够完成应用程序的安装。
另一条,所有人都可以安装供自己(而不是系统中的任何人)使用的应用程序。如果我希望玩某个游戏,我就应该能安装该游戏,而且不让系统中的其他任何人使用这一游戏。但请记住,非高级用户不能向 “Program Files” 目录写东西;该用户无法对 HKEY_LOCAL_MACHINE 进行写操作。在安装过程中,您应能打开 HKEY_LOCAL_MACHINE,查看您对它有没有写权限,然后让它作出如下声明:“您不能对该处进行写操作;是否希望这一应用程序只供个人使用?”
另一个安全性问题是默认的服务器权限。如果您试图安装服务器应用程序或服务,而使用的是非特权服务帐户(意即,您没有使用本地系统的帐户,也没有使用作为本地管理员组的成员所建立的帐户),则该帐户基本上不可能拥有实际运行服务所需的特权。在 Windows 2000 中,非特权用户(实际上是非特权服务帐户)所拥有的权限与 Windows NT 4.0 中不同。前者拥有的权限较少。例如,非特权用户无法在 Windows 系统目录的任何地方写入东西。如果您拥有一个正在运行的服务器,该服务器试图进行某些操作,或试图安装某些东西,但它可能没有具备适当的权限。如果您正在运行服务器应用程序,请确认它已登录了正确的帐户,并拥有正常运行所需的所有权限。
Windows 2000 兼容性问题
我要讨论的下一个问题被我称之为 Windows 2000 兼容性问题,这里指的是我们对 Windows 平台进行的某些更改,其目的是推动平台不断进步,并实现我们为用户提供更为可靠的平台的目标。这些改动将影响某些应用程序在 Windows 2000 上运行的方式。
设置前台窗口
我们以一个相当简单的操作开始:设置前台窗口。实际上,这一变动始于 Windows 98。不能指望只是简单地由应用程序调用 SetForegroundWindow ,然后您的窗口就能自动地变更为前台窗口。所有这些都是为了防止在任何人希望跳到最前台时出现令人烦恼的选项。您正在兴致勃勃地键入,突然弹出了一个窗口,要请求某种操作。您可能在键入过程中毫无意识地对某些根本不了解的东西作出了肯定的答复。
为了防止出现这种情况,对应用程序何时能成为前台应用程序制订了规则。其实在 Windows 98 中这些规则已经存在:
如果您的进程已经是前台进程,则可以取前台窗口。
如果您的进程刚刚由前台进程启动,则可以取前台窗口。
如果您的进程收到最后一次输入,则会变为前台窗口。
如果此时没有前台窗口,则可以取前台窗口。
如果正在对前台进程进行调试,则每个人都可以取前台窗口。
如果发生了前台超时锁定(某一段时间内前台锁定没有任何操作,而且不会作出响应),那么别人可以取得前台窗口。
如果当前任何系统菜单是活动的,则您的应用程序将无法取得前台。这主要是为了防止出现以下令人烦恼的情况:您正在使用“开始”菜单,并沿着它的分支菜单正要启动某个应用程序,突然这些菜单全部消失。 Windows 2000 中新增的这一规则可以防止出现这种情况。
超级隐藏文件
Windows 2000 中增加的另一个新特性称为“超级隐藏文件”(Super Hidden Files)。这里,系统会将几个文件同时标记“系统”和“隐藏”属性。文件仍位于原位置,我们还可以应用这些文件;只是当您进入 Windows 资源管理器之后,这些文件不会显示出来。即使选中“显示隐藏文件”,也无法看到它们。在文件夹的属性列表中有一个新的复选框,该复选框将允许用户看见这些文件,但普通用户不会选中该选项,所以无法看到这些文件。
而且,系统将不再显示 Windows 环境中那些几乎毫无用处的文件,其中绝大多数是基于 MS-DOS(R) 的旧文件以及类似文件。大多数情况下,这对普通用户没有影响;他们所看到的将是一个更简洁的系统。
对于 32 位应用程序来说,这其实不是兼容性问题。应用程序能够在常规的“打开文件”对话框中看到文件,并顺利地打开文件,命令行也依然有效。如果您采用能够查看超级隐藏文件的 Dir /ASH 命令,将会看到所有文件。
唯一存在兼容性问题的是 16 位应用程序,这类程序会碰到一点麻烦。这主要是由调用 MS-DOS 的 INT21 引起。如果您要求系统查找隐藏文件,MS-DOS 的 INT21 只会查找出隐藏的服务文件。
部分被隐藏的文件:
MS-DOS 系统文件,如 io.dos
Office 快速查找文件
NetBIOS
NetBIOS 一直是 Windows NT 的一部分。从 Windows 2000 开始,这一情况有所变化。它不是默认配置,用户可能对系统进行设置,使之不加载并不出现 NetBIOS。如果您的应用程序在没有 NetBIOS 的系统中调用使用 NetBIOS 的 API,则这些应用程序将无法继续正常工作,同时将返回错误。例如,如果采用诸如 NetServerEnum 的调用,而运行的系统中没有 NetBIOS,则将返回错误信息。您必须检查所有使用 NetBIOS 调用的地方,确定它们是否发生在没有 NetBIOS 的机器中,并进行正确处理。或者,可以将其替换成非 NetBIOS 调用。请确保您的用户知道您的系统始终需要 NetBIOS,并在安装程序或版本发布说明中明确告知。
需要新的网络 .inf 文件
如果您拥有任一网络设备(如网络驱动程序、传输驱动程序以及某些网络文件打印提供程序等),则您需要确认系统中存在该设备所需的新的网络 .inf 文件,以便支持 Windows 2000 即插即用。无论使用网络设备的系统是从头开始安装,还是由 Windows NT 4.0 升级到 Windows 2000,都要用到这些新文件。因这一格式与 Windows 98 兼容,所以您以前可能用过。无论如何,您都需要立刻将这些文件提供给用户,以便在系统升级到 Windows 2000 后,网络设备仍然能够得到支持。
物理驱动器号
如果您的应用程序需要以低级方式访问硬盘驱动器和卷(如病毒扫描程序),则将需要查找物理驱动器号,这时必须更改查找该号码的方式。过去,您可能使用符号链,该符号链返回的内容类似于:
\Device\HarddiskX\PartitionY
在其中的某个地方您会找到“Harddisk”以及后面的 X,并看出它是硬盘 2 或硬盘 3。而现在,符号链返回的是:
\Device\HarddiskVolumeZ
物理驱动器号将不再出现在这一符号链的任何地方。您需要用一对可用的 IOCTL 来代替。第一个是:
IOCTL_STORAGE_GET_DEVICE_NUMBER
该 IOCTL 只对单个物理驱动器号有效。例如,如果驱动器是 C 驱动器,甚至在一个驱动器上有多个分区,该 IOCTL 都将生效。但针对多卷集合,则需要使用:
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
该 IOCTL 对于 Windows NT 4.0 同样生效;从符号链中发现物理驱动器号总是有点危险的事情:在可能是多驱动器集的信息中,它们会只告诉您第一个驱动器。
访问磁带驱动器
如果您的应用程序要用到磁带驱动器,则您必须更改访问该磁带驱动器的方式。新的“层次结构存储管理”(Hierarchical Storage Management) 应用了称之为“可移动存储管理器”(Removable Storage Manager) 的工具,其主要操作过程如下:进入服务器并确认某个文件已很长时间未被访问。它会说:“让我们将它转到磁带上,如果某人需要该文件,我们可以将其找回,并让它脱离磁带”。用户将等待稍微长的时间,但可以获取该文件。这样,您可以使用一个似乎空间大得多的小驱动器。
因为“可移动存储管理器”正在服务器上频繁运行,而您的应用程序也在试图访问磁带驱动器,所以,应用程序会发现磁带驱动器总是处于忙碌状态,应用程序将无法控制磁带驱动器。在此讨论如何处理这一问题的篇幅不足。建议您在 Microsoft.com 上访问“Windows NT 5.0 存储应用程序开发过程中应考虑的问题”(Development Considerations for Storage Applications in Windows NT 5.0)。在该文中,您可以大致了解如何处理新的磁带驱动器。在实施这种处理之前,请花点时间浏览 SDK 中的 “可移动存储管理器程序员参考”(Removable Storage Manager Programmer's Reference),以便学习如何编写能与可移动存储管理器共享磁带驱动器的应用程序。
挂接显示驱动程序
如果您的应用程序试图“楔入”显示设备中(例如,您编写了一个显示驱动程序,它会先获得所有调用,然后再将它们移交给原始显示驱动程序),则需要改变操作方式。您已经见过进行这种操作的应用程序(远程控制应用程序即是一个例子),在这些例子中应用程序会取得显示驱动程序命令,并通过线路发送一个调用,然后在本地执行一个调用。如果要在 Windows 2000 中进行这种操作,则必须使用新的"显示驱动程序管理层"(Display Driver Management Level, DDML) 将该输出镜像到远程设备。这将启动多个显示驱动程序,而这正是远程控制应用程序正在进行的操作。这部分文档资料包含在 Windows 2000 Beta 3 版的 DDK 中。
写保护的内核模式
微软还采取了另一项措施增强平台的可靠性:任何运行于内核模式的程序都将在内存中实际拥有写保护区。如果您的设备驱动程序中使用了某些代码段或字符串段落,并且您在已列为只读区域的地方写入了某些临时内容(如注释等),这在 Windows 2000 中将是行不通的。我们不允许内核模式中的任何内容妨碍该处应该具备的保护功能,因为这会导致系统崩溃。
我们发现许多设备驱动程序没有遵守 Windows 2000 的这一规则。通过检查设备驱动程序,系统将判断如果设备驱动程序的设计目标是用于 Windows NT 4.0 而不是 Windows 2000,则不会强制执行该规则。如果不这样,将会导致太多的设备驱动程序无法正常工作。对于为 Windows 2000 编写的设备驱动程序或已进行升级、以便能在 Windows 2000 上正常工作的驱动程序,系统将强制执行该规则。
堆栈消费量增加
为了说明兼容性方面的几个实质问题,首先,我们必须明白 Windows 2000 所使用的堆栈空间要比 Windows NT 4.0 大得多。既然我们使用的是全球范围内统一的可执行程序,Unicode 所占用的空间就会比以往任何时候都要多,另外,我们还在这里或那里声明了更多的字符串,结果导致系统需要更多的堆栈。我们发现有些应用程序通过尽可能减少堆栈空间来增强性能。如果要提高运行速度,这无疑是个好主意:很明显,占用的内存越少,运行的速度就越快。但可惜的是,它们现在确实太小了,由于系统和应用程序一起很快用光了堆栈空间,结果是,应用程序随即崩溃。
为了确认您的应用程序中是否存在上述问题,需要检查以下设置:如果您在链接行中使用了 /STACK-linker 选项,则检查该选项;在编译器中检查在使用 STACKSIZE 参数或 /F 选项的 STACKSIZE-.def 文件。您需要重新检查所有这些内容,查看它们是否运行在 Windows 2000 上,并确认堆栈空间不是太小。
Win32 API 的变化
在 Windows 2000 中,Microsoft Win32(R) API 有许多改动;我们检查了其中几个,发现其中存在一些无意中造成的兼容性障碍。以下是我在 Windows 2000 测试过程中经常遇到的几处变化。
我们要在 Windows 2000 中支持一种新的输入法。为实现这一目的,需要传递 wParam 中的某些信息,这些信息通过 WM_KEYUP 和 WM_KEYDOWN 消息获取。我们要求您将 wParam 原封不动地传递给 TranslateMessage。如果您没有这样做,我们将无法全面实现这一新的输入法的功能。
另一个问题出在位于对话框结构内部的 DS_SHELLFONT 上。如果您指定了 DS_SHELLFONT,则不能再更改字体。我们使用 Microsoft Shell Dlg 2 作为字体;您可以更改大小,但却不能更改字形。
在“打开文件”对话框的 OPENFILENAME STRUCTURE 中,初始目录的行为有细小的差别。如果 OpenFile 没有找到任何您要查找类型的文件,默认情况下它将直接指向“My Documents”文件夹。
GetWindowsDirectory 返回的是针对每个用户的系统目录。如果您在终端服务器上,您可能会发现无法获得真正的系统目录,所获得的将是为特定用户设置的系统目录。有一个新的 GetWindowsSystemDirectory 调用,它能够在终端服务器上始终返回真正的系统目录。
应用程序稳定性问题
现在,将要讨论应用程序的稳定性问题,这些问题源于 Windows 2000 所发生的更改,由此在应用程序的实施或细节中发现了许多导致应用程序不兼容的错误。但是,所做的改动不会破坏应用程序。有时,当应用程序以少见的方式运行时也会出现这种问题。
硬代码路径
应用程序普遍采用“硬代码”引用方式,所以,当 Microsoft 对系统的某些地方作出改变,应用程序将因为它要寻找的内容不在原位置而无法工作,这里主要的罪魁祸首便是硬代码路径。在 Windows 2000 甚至 Windows NT 4.0 上,很多东西都被移动了位置。例如,在 Windows 9x 上“My Documents”文件夹只是位于 C 盘或 D 盘根目录下的一个文件夹,即:
\My Documents
Windows NT 将它移动了位置,并且针对每一用户,将其各自的文件夹放在 Windows 系统目录以下。因此在如下的例子中,即使文件夹被命名为“personal”,实际上它还是 Windows NT 4.0 上的“My Documents”文件夹。
%windir%\profiles\kylemar\personal
而 Windows 2000 则再一次移动了该文件夹的位置,使其不再位于系统目录下或根目录下。Windows 2000 将它放在:
\Documents and Settings\KYLEMAR\My Documents
正像您所看到的那样,当文件夹改变了位置,而如果硬代码仍然按照往常行事,当然会出现错误。事实上,在被管理环境中,“My Documents”文件夹也可能位于网络驱动器上。为了避免这种情况发生,就要像我们以前所讨论过的那样使用 SHGetFolderPath,并确保在 Windows 95、Windows 98 或任一平台上均采用这一方式。在默认的 Windows 2000 情况下,将完全可以找到正确位置。
长文件名和长打印机名
自从 Windows 95 问世之后,我们便一直在谈论长文件名及打印机名。最初,我们仅仅是要求应用程序能够支持这两者;在升级到 Windows 2000 后,我们要求程序能够正确地支持它们。我们发现在许多地方,应用程序并没有实现对长文件名的正确支持。但这并不是说这些应用程序对它们一点都不支持(尽管有少数的确如此),而是我们发现了应用程序在支持长文件名方面存在一些错误。例如,有一个应用程序,声称为实现对长文件名的支持,为全部 256 个字符提供了一个缓冲区。但是当我们将文件移走,并为应用程序提供了一个指向所寻找文件的较长路径(大约有 50 个字符)时,程序崩溃了。这表明尽管该应用程序告诉我们它拥有一个长缓冲区,而实际上只提供给我们一个较短的缓冲区。这只是应用程序中的一个简单的错误;由于我们将“My Documents”文件夹转移到了“Documents and Settings”,而不是将其置于根目录或 Windows 系统目录下,您会经常遇到这类错误。路径有越变越长的趋势,现在的平均路径长度是 60 到 70 字符,而不再是 30 到 40 字符。长路径名的使用正在暴露出越来越多的错误。
我们在“Documents and Settings”文件夹方面发现的另一个问题是“Documents and Settings”这种写法造成了许多应用程序无法正常访问到它。应用程序会对目录进行分析,只要发现“Documents”一词,程序就会认为到了“My Documents”的结尾。这样,应用程序会中断,同时认为“我发现了‘Documents’一词,我已经找到‘My Documents’了”。这当然行不通。
一定要全面检查长文件名支持功能,并进行测试。您会在 Windows 2000 应用程序规范(http://msdn.microsoft.com/certification/appspec.asp(英文))中发现一个相当长的字符串,可以使用它来确认应用程序是否可以正确地支持长文件名。
堆管理
另一个应用程序稳定性问题源于在 Windows NT 平台上对堆管理所进行的改动。这是我在这儿提到的最使人震惊的问题,它有极大的危险性,可以导致您的应用程序出现各种问题。它实际上与我们在旧版 C 语言中常常遇到的问题相同:出现了指针错误或内存使用错误。但它是很难处理的问题之一。
实际上这一问题起始于 Windows NT 4.0,Service Pack 4。我们对堆管理器进行了某些改动,目的是使它效率更高、运行更快,特别是针对拥有多处理器的计算机。Windows 2000 在此基础上又进行了一些更改,所以,能够在 Windows NT 4.0 上运行的程序,在安装 Service Pack 4 后无法运行。或者在 Service Pack 4 下可以运行的程序,在 Windows 2000 下却崩溃了,这是因为我们已经对堆管理器的工作方式作过很多改进。很明显,如果您要提高系统性能,并加快堆管理器的运行速度,可做的事情还是很多的。我们并未对 API 本身作出改动,也没有对堆的工作方式进行逻辑上的改动,但是我们对块的重复利用方式进行了一些微妙的改变,从而使应用程序中的错误暴露出来。
简而言之,我们所作的改动如下所示:以前,当一个块被释放出来,它将被列入未应用的空块列表,位于表的末尾,最后将被筛选出来再一次利用。现在,我们将缓存最后一次被使用的块,并把它们放在表的顶端,当您需要另一个块时,您更可能首先调回这一个块。如果您需要调回一个块,又生成了一个空块,您将会调回刚刚使用过的块,这样可以使自己保持在同一页,并且提高系统整个工作方式的速度。
这就是我们从一开始便在 C 语言设计及 C++ 程序开发中遇到的问题。实际上并没有更好地发现这些问题的途径。您可以利用一些商业工具去找出这些问题。除此之外,您需要在 Windows 2000 上尽可能彻底地测试这些软件。
我们发现许多应用程序都存在堆问题,最普遍的一个问题是试图访问已经释放的内存。结果是应用程序将进行分配,它将对这些块进行读写操作,然后释放这些块,而后又对这些块进行读写操作。这种做法的危险之处是会导致数据的破坏,您的应用程序就会崩溃。但是因为它驻留在已归您支配的某一块或页中,并且因为您正在读写允许您进行写操作的块,就不会导致存取侵犯,而是导致数据的破坏。但愿导致系统崩溃,因为比较而言这种情况更容易补救,但这也很难说,任何一种情况都可能发生。
另一出现问题的情况是:为了使某一页的空间得到更充分的利用,在您已经重分配了一个较小的块的情况下,Windows 2000 以及 Service Pack 4 还可能会移动这种分配区。很多开发人员持有一种受怀疑的优化观点,“如果我重新分配一个比我原先指定的块更小的块,将没有人可以把指针指向我,我就可以进行重分配,并相信不再移动的指针。因此只要我使它更小,就没有人可以移动它”。这样将会导致全面错误。
调用规则
下面讨论调用规则问题。资料上说,您必须为所有窗口过程使用 STDCALL。不幸的是,我们发现许多应用程序并没有为它的窗口过程使用 STDCALL,对话框过程也是如此。如果您不使用 STDCALL,您的程序可能无法正常工作。在 Windows 95 及 Windows 98 中,您可以通过使用 C_DECL 规则而避开这一问题;换句话说,如果您仅仅忘记在窗口过程中置入 C_DECL 规则,系统不会崩溃。
我们在 Windows 2000 中为此设置了功能区,因此我们可以尽可能地捕获它们。然而就在上一星期,我还是发现了一个错误,它跳出来对我说,“是的,我们已经使这些处理程序就位,并且,我们试着自己处理标准调用,但是因为应用程序没有使用标准调用,我们仍然没能正确截获它们。”如果您的窗口过程使用 STDCALL 而不是其他调用规则的话,事情便会简单得多。
我们也发现有些应用程序采用了正确的调用规则,却没有正确实施,或者是编译器中的错误显示出没有正确使用调用规则,或应用程序较快进入注册过程。因为我们正在进行进一步的优化,目的是更严格地使用注册内容并且使它占用资源更少、运行更快,因而,我们遇到过许多因未能严格遵循规则而导致应用程序不兼容的情况。
非缓冲文件的文件 I/O
如果您希望不使用系统提供的缓冲区处理一些文件 I/O,则需要使用 Create File FILE_FLAG_NO_BUFFERING 上的标志(意思是,您自己提供缓冲区,而不使用系统为这些读写操作而分配的缓冲区)。您必须确认传递给 ReadFile 和 WriteFile API 的缓冲区已针对设备进行了正确对齐。这些与以前没有什么区别,但是,我们发现,这些对齐方式在 Windows 2000 上稍微有些差别,特别在对新型 Ultra 66 IDE 驱动器等新设备的支持方面。所以,您必须确认分配缓冲区的方式是否正确。实现这一目的的最简便方式是使用 VirtualAlloc。VirtualAlloc 将始终将缓冲区按偶边界对齐,因此,无论设备完成文件 I/O 所需的缓冲区大小是多少,它总可以正确对齐。记住,当您进行这些操作的时候,您必须确认您的读写操作时使用的是 I/O 设备实际扇区大小的若干倍。您可以通过 GetDiskFreeSpace 得到扇区大小,并且确保您仅分配和读取这些扇区大小的数倍。
大型驱动器
另一个与驱动器有关的问题是某些大型驱动器所带来的。显然,目前的驱动器要比 4GB 大得多,一般来说,其中的可用空间也要比 4GB 多得多。然而如果您使用 GetDiskFreeSpace,它将返回一串 32 位数值,该数值并不是准确值,因为实际数值要比之多得多,这种情况会在很多地方出现。我们曾发现有的应用程序返回的磁盘空间的数量是负值,由此出现了各种各样的问题。
您需要应用 GetDiskFreeSpaceEx,它采用了 ULARGE_INTEGER_ 代替了INT,因此能够为您提供可用空间的真实数据。
打开另一个 HKEY_CURRENT_USER
有些时候有些应用程序(尤其是某些服务器应用程序),需要从其他 HKEY_CURRENT_USER 而不是它们最初或当前所使用的 HKEY_CURRENT_USER 中获取信息。问题在于 HKEY_CURRENT_USER 实际所采用的缓存方式是建立在每一进程的基础上。应用程序会试图关闭 HKEY_CURRENT_USER,模拟新的用户,然后打开 HKEY_CURRENT_USER,并希望他们获取的是正确的 HKEY_CURRENT_USER。问题是如果使用多个线程进行这一操作,其中的一个线程可能已经完成,而另一个线程可能正处于操作过程中。您无法搞清楚结束的是哪一个 HKEY_CURRENT_USER,因为第二次打开时系统会声明“我已打开了 HKEY_CURRENT_USER,我用的就是缓存中的那一个。”这种操作具有相当的危险性。为了改善这一状况,我们增加了一个新的、名为 RegOpenCurrentUser 的 API,有了该 API,您能够正确地实施模拟过程,以便获取真正需要的 HKEY_CURRENT_USER。
检查位 (Bit) 标志
另一个低级 C 类问题:我们已发现有的应用程序在对位标志进行检查时,使用的是等式运算符,而不是实际检查某一特定位是否存在。我们会在 Windows 2000 以及 Windows 2000 以后的所有版本中添加标志,因此您需要确保检查的是位,而不是是否相等。我们已添加了不带焦点值和加速值的所有者图形,以使其带有不同类型的绘图参数。以下是检查位标志的方式:
if (fItemState & ODS_FOCUS)
消息的顺序
我们一直在告诫开发人员不要使用消息的顺序或依靠应用程序接收消息的顺序去表达某种特殊含义。这是靠不住的。我们甚至发现有些应用程序在某些多线程应用中依赖消息的顺序。例如,有可能会发生下述情况:有一个线程关闭,然后向主消息循环发出一条信息,然后另一线程关闭,也发出一条消息。应用程序可能会将消息发出的顺序作为线程关闭的顺序。我们已对线程计划程序的操作方式进行了改动。理想情况下,它们完成了消息的发布,会依次将消息送入队列,以便进行处理,但实际情况并非如此。如果您尝试一下,会发现它们发出消息的顺序与线程关闭的顺序不一致,由此,就会发生各种问题。再次强调,如果您需要依靠消息的顺序(尤其是在跨线程的情况下),您不要想当然地认为消息本身就是进行同步的方式,而要增加自己的同步机制。
多个监视器
从 Windows 98 开始,Windows 具有了处理多个监视器的能力。Windows 2000 是第一个具有这种能力的、基于 Windows NT 的平台。由此出了一个大问题:您必须确认您的应用程序能正确处理负坐标和超大坐标或表现为超大坐标的情况。如果设置了多个监视器,而主监视器在副监视器的右侧,则副监视器将完全处于负坐标区域。如果您的应用程序调出一个应位于副监视器的窗口,而应用程序希望将窗口最大化,假设应用程序无法正确处理多监视器系统,它会因当前坐标完全为负而将窗口整个移动到主显示器中。这种情况是不应该发生的。如果您需要定位窗口,则一定要在多显示器系统中对应用程序进行测试,确认一定能正确处理这些负坐标。对于超大正向坐标也应如此处理。您需要采用图中所示的新的系统目标,确保将窗口置于它应处的位置。
Windows 平台之间的差异
我将讨论最后一类问题(与其他一些问题相比,这一问题要简要得多):Windows 平台的基本差异。Windows 2000 是以 Winsows NT 为基础的。我们认定大量用户会将他们的 Windows 9x 升级到 Windows 2000。我们一直在测试一些应用程序,将它们从 Windows 9x 移到 Windows 2000 平台。您将在杂志文章和 SDK 中发现关于这个问题的大量信息。您需要确保您的应用程序并不紧密针对 Windows 9x 平台,相反它应当紧密针对 Windows NT 平台。
目标严格限制的应用程序
第一种目标严格限制的情况是:应用程序使用的 API 仅能在 Windows 9x 平台上实现,在 Windows NT 中不能实现。我常常使用的 Tool Help API 就是这样的一个例子。在 Windows 2000 中我们已经可以在某种程度上支持 Tool Help API,但是您会发现在 Windows 9x 中有大量的 API 还没有找到升级到 Windows NT 的途径。没有一个真正可行的方法能解决这一难题。您可以使用 SDK 中的 .csv 文件,该文件实际上只是一个电子表格,它能告诉您关于任何 API 的情况:哪里可执行、哪里不能、如何工作,除此之外还有各种其他资料。另一种方式是对应用程序进行切实的测试,确保您的应用程序能够在 Windows 98 和 Windows NT 平台之间进行迁移,确保它们能够运行。这种方式可能要简单得多,执行起来也快得多。
您需要注意以下事实;Windows NT 平台在其 GDI 调用中使用的是全 32 位的坐标系统,而 Windows 9x 使用的是 16 位。一定要知道这些不同之处。事实上,Windows NT 中的所有句柄用的是完全的 32 位。有些开发人员试图利用以下事实:在 Windows 9x 中句柄是 32 位,但却只用到 16 位。如果在 Windows NT 上这样使用,后果将非常糟糕。
通用替换
在应用替换的过程中有许多应用程序也会陷入功能障碍。Windows 9x 所采用的方式可以称之为“直接替换 (flat thunk)”:它允许 16 位应用程序调入 32 位应用程序,也允许 32 位应用程序直接调入一个 16 位组件,或 16 位应用程序。Windows 2000 不支持这一功能,尤其是不支持 32 位应用程序直接调入 16 位应用程序。Windows 2000 和 Windows NT 所采用的方式可以称之为“通用替换 (generic thunk)”:通用替换允许 16 位应用程序调入 32 位组件,也允许 16 位应用程序启动对 32 位组件的调用过程,然后由 32 位组件回调 16 位应用程序,而不支持由 32 位代码直接调用 16 位组件,这一过程无法生效。您只能由 16 位启动对 32 位的调用,反之不能。另外针对替换过程还需要记住一点:即 Windows 9x 和 Windows NT 之间的基础进程模型都是有区别的。由通用替换中您可以看出一些区别。最简单的办法是将 16 位组件移植到 32 位组件。您需要对这两个平台的替换问题有清醒的认识。