荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Jobs (温少), 信区: Visual
标  题: Microsoft Windows 2000 应用程序兼容性
发信站: BBS 荔园晨风站 (Sun Feb 20 18:33:11 2000), 转信



Microsoft Windows 2000 应用程序兼容性
Kyle Marsh
Microsoft Corporation

1999 年 11 月

摘要:讨论使应用程序在 Microsoft(R) Windows(R) 2000 上存在不兼容性的
几个问题。其中有以下几部分:

介绍
设置和安装问题
Windows 2000 兼容性问题
应用程序稳定性问题
Windows 平台之间的差异

介绍
几个月来,我一直从事一项任务,即找出 Windows 2000 操作系统中的应用程
序兼容性问题。在这里我真正要讨论的是,造成应用程序与 Windows 2000 不
兼容的原因。没有人真正关心使应用程序兼容的原因。

我一直在与 Windows 2000 测试组合作,他们在过去的几个月中已测试了数百
个应用程序。我们已将应用程序在 Windows 2000 上正常或不正常运行的原因
进行书面论述。我们发现的问题可以归为四类:

无法在 Windows 2000 上安装的应用程序。 这是迄今我们发现的最大问题。应
用程序在 Windows 2000 上安装的方式并无甚特殊之处;问题是这些应用程序
不让自己安装到这一新版本的操作系统中。


我们对操作系统所做的、影响应用程序运行的更改。每当 Microsoft Windows
NT(R) 开发组面临选择,是使系统作为平台更稳定或更强大,还是保障应用程
序的兼容性,他们总是牺牲后者而取稳定性。Windows 2000 开发工作的一个主
要目标就是让系统作为平台更加稳定。遗憾的是,为了实现这一点而必须进行
的某些改动,已导致应用程序在 Windows 2000 上不兼容。



我们已对操作系统进行的更改不会影响应用程序的兼容性,但会中断某些应用
程序。


过于依赖 Windows 9x 平台的应用程序。我们在开发 Windows 2000 时,考虑
到有众多 Windows 9x 用户需要升级,因此对 Windows 9x 应用程序进行了测
试,将它们移植到 Windows 2000 中。我们发现某些应用程序过于依赖
Windows 9x。

设置和安装问题
我们要讨论的第一类问题是设置和安装问题;最常见的问题无疑是无法在
Windows 2000 上安装应用程序。实际上,导致无法安装应用程序的一个最普遍
的原因,在于 Windows 2000 是 Windows NT 的 5.0 版。

测试组以多种方式测试应用程序。他们将应用程序安装在基于 Windows 2000
的系统中,或者将应用程序安装在 Windows NT 4.0 或 Windows 95 中,然后
再将系统升级到 Windows 2000,以便进行测试。

我们拿来一台未安装任何操作系统的机器后,安装上 Windows 2000,再安装应
用程序,与上述升级的情况相比,前者的兼容性数目要少得多。


版本检查
造成应用程序无法安装在 Windows 2000 上的第一位原因,是它们无法正确处
理版本号。我们发现很多应用程序都进行以下示例代码所做的操作。它们在运
行过程中会调用 GetVersionEX,然后写下一条“if”语句,该语句规定:“如
果系统是版本 3,因为没有新的 Shell,我不能正常运行,所以我可能无法安
装。如果系统是版本 4,我可以进行安装和设置”。问题出在如果系统是版本
5,这一“if”语句就没有了下文。因为版本号是 5.0,这些应用程序由于自身
原因而无法安装,所以我们发现了一系列这样那样的问题。

if (osvi.dwMajorVersion == 3)
   {
   // 请这样做
   }
else if (osvi.dwMajorVersion == 4)
   {
   // 请那样做
   }

测试组继续寻找解决方案,并蒙蔽了许多此类应用程序。在早期的编译中,我
们能够采取措施改变 GetVersionEx 的返回值。我们可以改变其返回值,欺骗
应用程序,告诉它版本号就是 4.0,然后程序就能够继续安装,并正常运行。
但有部分应用程序的设计思想就是不能安装在 Windows 2000 上。对于病毒扫
描程序或其他低级实用程序来说,受限于某一操作系统版本是可以理解的。不
过,这些应用程序会显示消息来说明这一点。我们查找的是那些不能安装或无
法正常运行、又根本没有通知用户的应用程序。

怎样才能正确地检查版本号?在 Windows 2000 中我们将添加一个新的 API:
VerifyVersionInfo,这一 API 在运行时将依次检查主版本号、次版本号以及
服务包。如果出现了操作系统的新版本,应用程序仍然能够在其上安装并运行
。实际上应用 VerifyVersionInfo 的选项和方式还有很多,但如果只是检查“
要是操作系统升级了,应用程序该如何处理”这一类问题,您只需调用这三个
标志,然后检查主版本号、次版本号以及服务包。您能够定义以下语句:“我
的程序需要运行在 Windows NT 4.0、SP2 上”,然后询问 VerifyVersionInfo
“我正在运行的操作系统是否已达到这一标准?”,该 API 将返回真值或假值



VerifyVersionInfo(&osvi,
      VER_MAJORVERSION |
      VER_MINORVERSION |
      VER_SERVICEPACKMAJOR,
      dwlConditionMask);

采用这一方式检查版本,就可以符合 Windows 2000 应用程序的规范,其基本
思想是“只要存在新版本的操作系统,就要在新版本上进行安装。”

应用 VerifyVersionInfo 的一个问题是目前该 API 只能在 Windows 2000 平
台上运行。为了检查 Windows 95 等旧平台的版本,您必须应用GetVersionEx
。查看以下示例代码,即可发现它的功能与 VerifyVersionInfo 基本相同:依
次检查主版本号、次版本号以及服务包。

BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )
   {
   OSVERSIONINFO osvi;
   // 初始化 OSVERSIONINFO 结构
   //
   ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
   GetVersionEx((OSVERSIONINFO*)&osvi);
   // 首先,主版本
   if ( osvi.dwMajorVersion > dwMajor )
      return TRUE;
   else if ( osvi.dwMajorVersion == dwMajor )
      {
      // 然后,次版本
      if (osvi.dwMinorVersion > dwMinor )
         return TRUE;
      else if (osvi.dwMinorVersion == dwMinor )
         {
         // 对,最好检查一下 Service Pack
         if ( dwSPMajor && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT )
            {
            HKEY   hKey;
            DWORD   dwCSDVersion;
             DWORD   dwSize;
            BOOL   fMeetsSPRequirement = FALSE;

            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                         "System\\CurrentControlSet\\Control\\Windows", 0,
                         KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
               {
               dwSize = sizeof(dwCSDVersion);
               if (RegQueryValueEx(hKey, "CSDVersion",
                        NULL, NULL, (unsigned char*)&dwCSDVersion,
                        &dwSize) == ERROR_SUCCESS)
                  {
                  fMeetsSPRequirement = (LOWORD(dwCSDVersion) >= dwSPMajor);
                  }
                RegCloseKey(hKey);
                }
            return fMeetsSPRequirement;
            }
         return TRUE;
         }
      }

   return FALSE;

   }

//
// 此示例适用于 Windows 2000 和更新版本
//
BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )
    {

    OSVERSIONINFOEX osvi;
   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
   osvi.dwMajorVersion = dwMajor;
   osvi.dwMinorVersion = dwMinor;
   osvi.wServicePackMajor = dwSPMajor;
   // 设置条件掩码。
   VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL );
   VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL );
   VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL
);
   // 执行测试。
   return VerifyVersionInfo(&osvi,
                             VER_MAJORVERSION | VER_MINORVERSION
                             | VER_SERVICEPACKMAJOR,dwlConditionMask);
     }

首先,需要检查主版本号。如果当前操作系统的主版本号高于所需主版本号,
则不需要进行任何检查,直接向下运行即可。如果主版本号相等,则以同样方
式检查次版本号。最后再检查服务包号。在我们获得发布的某一版本时,就可
以说“没关系,我们不在乎是 Service Pack 3、4 还是 5”。如果主版本号或
次版本号有所增长,系统同样可以处理。我们查找了所有要检查版本信息的应
用程序,发现它们将分别检查每一组件。它们在运行中会说“噢,主版本号是
5,我只要求 4,不错。次版本号是 .0,很好,但 Service Pack 是 0,我需
要的是 3”。很明显,Windows 2000 还没有推出 SP3,所以这种检查版本信息
的方法是错误的。

检查 Windows NT 的版本号时需要注意以下细节:检查版本号和服务包信息的
方法有三种。第一种是获取 GetVersionEx 的返回值,然后检查“
szCSDVersion”字符串。实际的服务包号嵌入在该字符串的某一位置。分析这
一串字符实在是比较繁琐,而且您必须始终牢记它是否进行了本地化,这是很
难做到的。这不是一种最好的方式。如果系统运行的是 Windows NT 4.0,您只
需检查以下注册键值,其中有一个数字是服务包号。提取这一数字,并进行一
个“等于”或“大于”比较即可:


HCLM\System\CurrentControlSet\Control\Windows\CSDVersion

如果您运行的是 Windows 2000 或更高版本,则仍可以使用 GetVersionEx,但
需要将其传递到 OSVERSIONINFOEX 结构,而不是正在使用的OSVERSIONINFO 结
构。考虑到起始处操作员成员的大小,Windows 2000 会将其视为一个较大的结
构,并且给出一个新的字段(服务包、主版本、次版本),作为进行比较时所
用的整数。如果您还是使用 VerifyVersionInfo,您会发现这是最方便的一种
方式。

DLL 版本检查
在检查 Windows 版本的同时,我们还发现了另一个与版本相关的问题,即用户
不检查 DLL 的版本。无论 DLL 是系统目录中的 Microsoft DLL,还是您自己
的 DLL,在将其复制到系统前,都必须进行版本检查。检查的目的是防止将旧
版的 DLL 复制到新版的上面。

必须确认在用户自己的 DLL 中已添加了版本信息,以便进行检查。进行这一操
作非常重要,它能够避免出现各种麻烦。不要试图更改系统目录中的 DLL,甚
至不要考虑对系统 DLL 进行升级或降级,或覆盖同一 DLL。系统 DLL 是由
Windows 2000 在 CD 或服务包中提交的、位于系统目录中的 DLL。

如果您打算编制新的 Windows 2000 应用程序,则需要使用 Windows
Installer,它能够为您检查 DLL 的版本。只要说明需要将某一特定的 DLL 置
于特定的目录中,即可发现 Windows Installer 在为您进行版本检查工作。您
不需要再处理这一小块代码。



DLL Hell
如果没有正确地检查 DLL 版本,结果毋庸质疑会发生 DLL Hell。我肯定不需
要向您解释什么是 DLL Hell,您一定在这上面花费了不少时间。因为我最初曾
参与开发 ctl3d.dll,对这些东西就象熟悉我的邻居一样。

我们花费了几年的时间,努力去突破那些影响 DLL 正常工作的障碍,我们最终
认定应用程序开发商无法保持后向兼容性。每个人都在朝这方面努力,这一目
标很伟大,但却无法实现。实际上,DLL 不可能保持后向兼容性。结果就是:
某一应用程序如果要正常结束,必须取决于某一 DLL 的某个特定版本,而另一
应用程序则取决于该 DLL 的另一版本,因为这两个应用程序在这一共享组件(
即共享 DLL)方面发生了冲突,导致无法共存于同一系统中。


到目前为止,我们仍然认为对于 Windows 应用程序来说,DLL 共享功能是一个
非常良好和重要的组成部分。我们同样认为 DLL 能为您提供的功能还是非常有
价值的。问题是如果不能测试 DLL 的版本,而跨应用程序对 DLL 进行全局共
享会带来非常多的问题。

并行 DLL
在 Windows 2000 中,我们开始实施某些称之为并行 DLL 的内容。我们希望您
开发的应用程序也开始向并行版本策略靠拢。在 Windows 2000 中,我们采取
了一些预防性措施,以减少 DLL Hell。我们要考虑的第一位的事情是无论安装
了何种应用程序,都要保证系统处于受保护状态,保持其完整性。随后我们将
讨论 Windows 文件保护。


我们要做的另一件事情是实现组件的并行,同时希望应用程序供应商也这样做
。我们针对的是微软的组件;至于您自己的组件,我们也建议您这样做。我们
将采用并行版本功能,而对于您自己目前正在进行全局共享的组件,我们也希
望您能采用并行版本功能。


系统稳定性
Windows NT 小组正在努力进行的另一项工作是确保系统保持长时间的稳定。微
软允许通过分发服务包的形式将各种功能吸纳到 Windows NT 中,这是一个问
题。安装了 Windows NT 的客户和公司在选取服务包时已非常警惕,因为这些
东西绝不仅仅是在更正某些问题。通常情况下它会作出某些更正,然后说“噢
,我们在这里添加了这个特性,在那里添加了那个功能”,这些东西使得系统
无法达到所预期的稳定性。


按照常规策略,服务包只能包含对错误的更正,不能有其他内容。如果我们认
为操作系统中需要新增某些重要的特性和功能,我们将推出 Windows 2000 的
.x 版本。您会得到类似 Windows NT 5.1、5.2 等此类内容,另外还有针对
Windows NT 的三个不同版本发布的服务包。我们将继续努力保持每一平台功能
的完整性,其中甚至涉及到 QFE、错误检查和 hot fix。每次有了新的功能集
或新特性,都会发布新版操作系统。


我们在微软所进行的最后一项工作是确保了解随同产品发布了哪些组件,我们
强烈建议您遵照执行。我们正在尽最大可能地减少不同产品中发布的组件的数
量。如果某一特定组件需要与另一特定组件协同工作,我们会尽量将这两个组
件一同发布。对于所有这些能够重新分发的组件,我们将定出发布的结构顺序


并行 DLL
如果需要将组件由全局共享组件或 DLL 更改为新的并行 DLL,需要对 DLL 进
行某些改动。这种对 DLL 自身的改动是必须的。您必须得声明:“我所设计的
组件将允许同时运行多个版本。”

为了使某一组件成为真正的并行组件,首先需要对 DLL 进行重命名,并且更改
可能存在于 OCX 控件、COM 对象中的所有 GUID。这种重命名的工作只需进行
一次,就能保证您获得一个并行运行的新 DLL,该 DLL 将不再是全局共享。


DLL 被重命名后,应用程序会将其安装到自主管理的目录中,而不会安装到系
统目录。这样,应用程序开发人员就可以说:“我已对带有这一 DLL 的特定版
本的产品进行了全面测试,而且我还确认在我再次进行测试之前,这一 DLL 不
会进行升级。”这就给了做为开发员的您足够的信心:任何人无法使用共享组
件扰乱您的应用程序,导致系统崩溃并将我们带回到 DLL Hell。

如果您是作为用户使用这些组件,您可以在自己的目录(而不是系统目录)中
注册一个相对路径。这样会加载一个落在系统某处的本地版本,而不是全局副
本。

我们对 LoadLibrary 功能进行了修改,从而确保:如果应用程序以相对路径注
册了一个组件,我们也始终以相对路径完成加载,而不管这一组件是位于系统
目录中,还是运行在别的什么地方。由此可以确保您获得用以测试应用程序的
那一份副本。

隔离的应用程序
我们还修改了 LoadLibrary 代码,以便支持 DLL 重定向。由此管理员可以将
DLL 的加载过程重定向到某一位置,并由本地目录加载 DLL。经过这一处理后
,您的 DLL 就可以处于隔离状态。假设某一大单位的某个人要测试他们能否采
用您的应用程序,他们安装了另一应用程序,然后测试这两者能否协同工作。
他们发现结果是不能,管理员就开始查找这两个应用程序在何处,在哪一组件
上发生了冲突。找到这一组件后,管理员从同时使用这两个程序的雇员的角度
进行了考虑,提取 DLL(或包含对象的 OCX),并将其置于应用程序所在的目
录。然后管理员创建了一个名为 foo.exe 的文件,其后又加上 .local。如果
调用 LoadLibrary,LoadLibrary 发现这里有一个 foo.exe.local 文件,它会
首先加载应用程序目录中的文件,而不会考虑应用程序用于 LoadLibrary 调用
本身的特定路径。这种方式有助于人们区分需要同一组件的不同版本的多个应
用程序,使所有这些应用程序运行于同一系统中。


Windows 文件保护
为了确保系统的稳定性和平台的可靠性,第一步就是保障系统不会遇到任何
DLL Hell 问题。我们希望无论发生了什么事情,系统仍然能够运行,能够引导
,即用户可以对系统的稳定性有充分的信心。

有了 Windows 文件保护 (WFP),如果应用程序试图更改某一系统文件,
Windows 2000 会将其恢复原状。对部分功能,应用程序会安装并说:“瞧,我
需要这个 DLL 的新版本…”去实现某一功能,或根本这就是一个错误的应用程
序,不会正确地执行版本检查功能。Windows 2000 将检查这一点,会发现文件
已改动。如果 Windows 2000 发现这是一个系统文件,并声明“我不允许改动
这一文件”,它会将文件恢复原状。

如果要升级那些已被系统锁定的文件,只能采用 Windows NT 小组所发放的几
种文件替换机制:服务包、QFE 或 hot fix。它们能够实现对系统文件的替换
,而其他应用程序却不能。

举个例子,mfc42.dll 是我们锁定的一个文件。通过语言组自身将不能再升级
该 DLL,只有 Windows NT 小组能够更改系统中的这一文件。如果 C 程序设计
人员需要升级他们的 DLL(而且假定他们希望在 Windows 2000 上市之后而下
一版的 Windows NT 发布之前进行升级),只能采用并行的组件版本功能。


大多数 *.sys、*.dll、*.exe 和 *.ocx 文件以及几个字体文件在保护之列。


如此还存在几个兼容性问题。首先,防病毒程序必须认识并正确处理 Windows
文件保护功能,再在此基础上进行应用程序的备份和恢复;不能简单地对这些
文件进行复制、备份和恢复。因为您没有系统所支持的文件替换机制,如果您
这样做,将取消 Windows 文件保护功能。


为了防止人们进行这类操作,我们在系统中添加了几个 API。

WFP API
第一个 API 是 SFCGetNextProtectedFile。可用这一 API 可以获得所有受保
护或能保护的文件的清单。您可以以一个空值开始重复调用这一 API,以获得
受保护文件的列表。


BOOL WINAPI SfcGetNextProtectedFile
(IN HANDLE RpcHandle,IN PPROTECTED_FILE_DATA ProtFileData );
//
// 此功能将列出受保护文件
//
void ListProtectedFiles(HWND hWnd)
   {
   HWND   hwndList;
   PROTECTED_FILE_DATA pfd;
   int iCount;
   char szFileName[260];
   int iLen;
   RECT rt;

   hwndList = GetWindow(hWnd,GW_CHILD);
   if ( hwndList == NULL )
      {

      GetClientRect(hWnd, &rt);
      // 第一次创建“列表”控件
      hwndList = CreateWindow("LISTBOX", NULL,
                        WS_CHILD | WS_VISIBLE | LBS_STANDARD |
LBS_NOINTEGRALHEIGHT |
                        LBS_USETABSTOPS,
                        0,20,rt.right,rt.bottom-40,
                        hWnd,
                        NULL,
                        hInst,
                        NULL);
      }
   else
      SendMessage(hwndList, LB_RESETCONTENT, 0, 0);

   ZeroMemory(&pfd,sizeof(PROTECTED_FILE_DATA));
   iCount = 0;
   while ( g_pfnSfcGetNextProtectedFile(NULL, &pfd) != 0 )
      {
     // 为此“ANSI 应用程序”将 WCHAR 转换到 ANSI
      iLen = WideCharToMultiByte(CP_ACP,NULL,pfd.FileName, wcslen(pfd.FileName),
                      szFileName,260,NULL,NULL);
      szFileName[iLen] = '\0';
      SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)szFileName);
      iCount++;
      }
   }

另一个更为直接的 API 是 SfcIsFileProtected。该 API 能更为便捷地为绝大
多数应用程序直接调用,并回答以下问题:“看看这一文件,它是否受到保护
?”但是请记住,它需要指向这一文件的完整路径。您不能只是简单地指定
NTS.sys,而是需要给出到达 NTS.sys 所处位置的路径。如果您将这一文件名
传递给 API,它会说:“是的,这个文件已受到保护”,或“这不是一个受保
护的文件”。在您进行任何备份或恢复操作时都需要使用该 API。如果您希望
进行任何安装设置,或可能会更新某一系统文件,您都可以调用这一 API。如
果您希望将某一目标文件置于系统目录中,在进行这一操作之前,您需要调用
这一 API,以避免取消 Windows 文件保护功能。下一版的 Windows Installer
(与 Windows 2000 一同发布)将在复制文件之前进行检查,因此不会意外地
启动 WFP。

BOOL WINAPI SfcIsFileProtected (IN HANDLE RpcHandle,IN LPCWSTR ProtFileName);

//
// 此函数使用“文件”打开对话框,以便从用户获取文件名并检查其是否受保护。
void CheckFileForProtection(HWND hWnd)
   {
   OPENFILENAME OpenFileName;
   CHAR szFile[MAX_PATH]      = "\0";
   CHAR szSystem32[MAX_PATH];
    strcpy( szFile, "");
   ZeroMemory(&OpenFileName, sizeof(OPENFILENAME));
   // 填充 OPENFILENAME 结构以支持模板和挂接。
   OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = hWnd;
    OpenFileName.hInstance         = hInst;
    OpenFileName.lpstrFile         = szFile;
    OpenFileName.nMaxFile          = sizeof(szFile);
    OpenFileName.lpstrTitle        = "Select a File";
    OpenFileName.Flags             = OFN_FILEMUSTEXIST;

   if (g_pfnSHGetFolderPath != NULL )
      g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);
   else
      szSystem32[0] = '\0';
   OpenFileName.lpstrInitialDir   = szSystem32;
   // 调用公共对话函数。
    if (GetOpenFileName(&OpenFileName))
      {
      // 检查文件
      WCHAR wzFileName[260];
      int iLen;
      iLen = MultiByteToWideChar(CP_ACP,NULL,szFile, strlen(szFile), wzFileName,
 260);
      wzFileName[iLen] = '\0';
      if (g_pfnSfcIsFileProtected(NULL, wzFileName) == TRUE )
         {
         MessageBox(hWnd,"Is Protected", szFile, MB_OK);
         }
      else
         MessageBox(hWnd,"Is NOT Protected", szFile, MB_OK);

      }

组件检查
我们发现导致无法在 Windows 2000 上安装应用程序的另一个原因是组件检查
功能。显然,我们操作系统的每一版本都是由多个不同的组件组成。这些组件
包括 TAPI、MAPI、Microsoft DirectX(R) 等等。我们发现应用程序会对什么
组件处于什么位置,以及是否存在某一组件作出自己的假设。应用程序会因为
甲组件存在而假定乙组件也存在,因为某一组件的版本 2 存在而认定另一组件
的版本 3 也必然存在。如果您需要用到某一组件,则一定要检查系统中是否存
在这一组件,以及它是否位于正确的级别。

除此之外,开发人员还会假设 Windows NT 中没有 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 等任一子目录
。虽然这样做本身没有什么坏处,过去我们也常常希望您如此,因此我们无法
完全禁止这一做法,但我们现在希望系统中的所对S HFolder.dll
进行分发。该 DLL 将解开这一名为 SHGetFolderPath 的新 API 的外壳。这一
API 了解每一特殊文件夹在系统中所处的位置。您可以在 SDK 中找到该 API。
它是一个非常长的清单,其中列出了您能够考虑选用的每一个文件夹。


需要注意,旧版的平台只支持以下四个 CSLID。如果您要查找某一特定目录,
发现该目录不存在,SHGetFolderPath 能够为您创建该目录,条件是您已指定
了这一操作,但在 Windows 95 等后向平台中,这一过程只能对以下四个
CSIDL 有效:

CSIDL_PERSONAL

CSIDL_APPLICATIONDATA

CSIDL_MYPICTURES

CSIDL_LOCAL_APg_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 出现的
几个安全性问题:

高级用户应能安装整个系统范围的应用程序。我们发现有些应用程序坚持只能
由管理员进行这类安装。对于锁定的机器,企业仍然希望许多不同用户,或一
天中有两三个用户能够进行共享;或者是在特定的某一天,所有雇员都能够使
用这一系统。他们只是不希望任何人都能以管理员权限对系统任意更改,为所
欲为。蟹裾驶В┧涤械娜ㄏ抻? Windows NT 4.0 中不同。前者拥有的权限较少。
例如,非特权用户无法在 Windows 系统目录的任何地方写入东西。如果您拥有
一个正在运行的服务器,该服务器试图进行某些操作,或试图安装某些东西,
但它可能没有具备适当的权限。如果您正在运行服务器应用程序,请确认它已
登录了正确的帐户,并拥有正常运行所需的所有权限。

Windows 2000 兼容性问题
我要讨论的下一个问题被我称之为 Windows 2000 兼容性问题,这里指的是我
们对 Windows 平台进行的某些更改,其目的是推动平台不断进步,并实现我们
为用户提供更为可靠的平台的目标。这些改动将影响某些应用程序在 Windows
2000 上运行的方式。


设置前台窗口
我们以一个相当简单的操作开始:设置前台窗口。实际上,这一变动始于
Windows 98。不能指望只是简单地由应用程序调用 SetForegroundWindow ,然
后您的窗口就能自动地变更为前台窗口。所有这些都是为了防止在任何人希望
跳到最前台时出现令人烦恼的选项。您正在兴致勃勃地键入,突然弹出了一个
窗口,要请求某种操作。您可能在键入过程中毫无意识地对某些根本不了解的
东西作出了肯定的答复。


为了防止出现这种情况,对应用程序何时能成为前台应用程序制订了ows 2000 中增加的另
一个新特性称为“超级隐藏文件”(Super Hidden
Files)。这里,系统会将几个文件同时标记“系统”和“隐藏”属性。文件仍
位于原位置,我们还可以应用这些文件;只是当您进入 Windows 资源管理器之
后,这些文件不会显示出来。即使选中“显示隐藏文件”,也无法看到它们。
在文件夹的属性列表中有一个新的复选框,该复选框将允许用户看见这些文件
,但普通用户不会选中该选项,所以无法看到这些文件。

而且,系统将不再显示 Windows 环境中那些几乎毫无用处的文件,其中绝大多
数是基于 MS-DOS(R) 的旧文件以及类似文件。大多数情况下,这对普通用户没
有影响;他们所看到的将是一个更简洁的系统。


对于 32 位应用程序来说,这其实不是兼容性问题。应用程序能够在常规的“
打开文件”对话框中看到文件,并顺利地打开文件,命令行也依然有效。如果
您采用能够查看超级隐藏文件的 Dir /ASH 命令,将会看到反怼;蛘撸梢越涮婊怀煞
? NetBIOS 调用
。请确保您的用户知道您的系统始终需要 NetBIOS,并在安装程序或版本发布
说明中明确告知。


需要新的网络 .inf 文件
如果您拥有任一网络设备(如网络驱动程序、传输驱动程序以及某些网络文件
打印提供程序等),则您需要确认系统中存在该设备所需的新的网络 .inf 文
件,以便支持 Windows 2000 即插即用。无论使用网络设备的系统是从头开始
安装,还是由 Windows NT 4.0 升级到 Windows 2000,都要用到这些新文件。
因这一格式与 Windows 98 兼容,所以您以前可能用过。无论如何,您都需要
立刻将这些文件提供给用户,以便在系统升级到 Windows 2000 后,网络设备
仍然能够得到支持。

物理驱动器号
如果您的应用程序需要以低级方式访问硬盘驱动器和卷(如病毒扫描程序),
则将需要查找物理驱动器号,这时必须更改查找该号码的方式。过去,您可能
使用符号链,该符号链返回的那会只告诉您第一个驱动
器。

访问磁带驱动器
如果您的应用程序要用到磁带驱动器,则您必须更改访问该磁带驱动器的方式
。新的“层次结构存储管理”(Hierarchical Storage Management) 应用了称
之为“可移动存储管理器”(Removable Storage Manager) 的工具,其主要操
作过程如下:进入服务器并确认某个文件已很长时间未被访问。它会说:“让
我们将它转到磁带上,如果某人需要该文件,我们可以将其找回,并让它脱离
磁带”。用户将等待稍微长的时间,但可以获取该文件。这样,您可以使用一
个似乎空间大得多的小驱动器。

因为“可移动存储管理器”正在服务器上频繁运行,而您的应用程序也在试图
访问磁带驱动器,所以,应用程序会发现磁带驱动器总是处于忙碌状态,应用
程序将无法控制磁带驱动器。在此讨论如何处理这一问题的篇幅不足。建议您
在 Microsoft.com 上访问“Windows NT 5.0 存储应用程序开发过程中应考虑
的蜗允厩绦蚬芾聿?"(Display Driver Management
Level, DDML) 将该输出镜像到远程设备。这将启动多个显示驱动程序,而这正
是远程控制应用程序正在进行的操作。这部分文档资料包含在 Windows 2000
Beta 3 版的 DDK 中。

写保护的内核模式
微软还采取了另一项措施增强平台的可靠性:任何运行于内核模式的程序都将
在内存中实际拥有写保护区。如果您的设备驱动程序中使用了某些代码段或字
符串段落,并且您在已列为只读区域的地方写入了某些临时内容(如注释等)
,这在 Windows 2000 中将是行不通的。我们不允许内核模式中的任何内容妨
碍该处应该具备的保护功能,因为这会导致系统崩溃。


我们发现许多设备驱动程序没有遵守 Windows 2000 的这一规则。通过检查设
备驱动程序,系统将判断如果设备驱动程序的设计目标是用于 Windows NT 4.0
而不是 Windows 2000,则不会强制执行该规则。如果不这样,将会导致太多的
设备谏鲜鑫侍猓枰觳橐韵律柚茫喝绻诹?
接行中使用了 /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 ndows
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

正像您所看到的那样,当文件脊徽返刂С炙恰N颐欠⑾衷谛矶嗟胤剑τ贸绦虿⒚挥惺
迪侄?
长文件名的正确支持。但这并不是说这些应用程序对它们一点都不支持(尽管
有少数的确如此),而是我们发现了应用程序在支持长文件名方面存在一些错
误。例如,有一个应用程序,声称为实现对长文件名的支持,为全部 256 个字
符提供了一个缓冲区。但是当我们将文件移走,并为应用程序提供了一个指向
所寻找文件的较长路径(大约有 50 个字符)时,程序崩溃了。这表明尽管该
应用程序告诉我们它拥有一个长缓冲区,而实际上只提供给我们一个较短的缓
冲区。这只是应用程序中的一个简单的错误;由于我们将“My Documents”文
件夹转移到了“Documents and Settings”,而不是将其置于根目录或
Windows 系统目录下,您会经常遇到这类错误。路径有越变越长的趋势,现在
的平均路径长度是 60 到 70 字符,而不再是 30 到 40 字符。长路径名的使
用正在暴露出越来越多的错误。


我们在“Documents and Settings”文件夹方面发现的另一个问题是“
Documents and Settings”这种写法造成了许多应用程序无法正常访问到它。
应用程序会对目录进行分析,只要发现“Documents”一词,程序就会认为到了
“My Documents”的结尾。这样,应用程序会中断,└模裕芄辉? Windows
NT 4.0 上运行的程序,在安装 Service Pack 4 后无法运行。或者在 Service
Pack 4 下可以运行的程序,在 Windows 2000 下却崩溃了,这是因为我们已经
对堆管理器的工作方式作过很多改进。很明显,如果您要提高系统性能,并加
快堆管理器的运行速度,可做的事情还是很多的。我们并未对 API 本身作出改
动,也没有对堆的工作方式进行逻辑上的改动,但是我们对块的重复利用方式
进行了一些微妙的改变,从而使应用程序中的错误暴露出来。


简而言之,我们所作的改动如下所示:以前,当一个块被释放出来,它将被列
入未应用的空块列表,位于表的末尾,最后将被筛选出来再一次利用。现在,
我们将缓存最后一次被使用的块,并把它们放在表的顶端,当您需要另一个块
时,您更可能首先调回这一个块。如果您需要调回一个块,又生成了一个空块
,您将会调回刚刚使用过的块,这样可以使自己保持在同一页,踩菀撞咕龋?
也很难说,任何一种情况都可能发生。

另一出现问题的情况是:为了使某一页的空间得到更充分的利用,在您已经重
分配了一个较小的块的情况下,Windows 2000 以及 Service Pack 4 还可能会
移动这种分配区。很多开发人员持有一种受怀疑的优化观点,“如果我重新分
配一个比我原先指定的块更小的块,将没有人可以把指针指向我,我就可以进
行重分配,并相信不再移动的指针。因此只要我使它更小,就没有人可以移动
它”。这样将会导致全面错误。


调用规则
下面讨论调用规则问题。资料上说,您必须为所有窗口过程使用 STDCALL。不
幸的是,我们发现许多应用程序并没有为它的窗口过程使用 STDCALL,对话框
过程也是如此。如果您不使用 STDCALL,您的程序可能无法正常工作。在
Windows 95 及 Windows 98 中,您可以通过使用 C_DECL 规则而避开这一问题
;换句话说,如果您仅仅忘记在窗口过程中置入 C_DECL 规则,匣些文件 I/O,则需要使用
 Create
File FILE_FLAG_NO_BUFFERING 上的标志(意思是,您自己提供缓冲区,而不
使用系统为这些读写操作而分配的缓冲区)。您必须确认传递给 ReadFile 和
WriteFile API 的缓冲区已针对设备进行了正确对齐。这些与以前没有什么区
别,但是,我们发现,这些对齐方式在 Windows 2000 上稍微有些差别,特别
在对新型 Ultra 66 IDE 驱动器等新设备的支持方面。所以,您必须确认分配
缓冲区的方式是否正确。实现这一目的的最简便方式是使用 VirtualAlloc。
VirtualAlloc 将始终将缓冲区按偶边界对齐,因此,无论设备完成文件 I/O
所需的缓冲区大小是多少,它总可以正确对齐。记住,当您进行这些操作的时
候,您必须确认您的读写操作时使用的是 I/O 设备实际扇区大小的若干倍。您
可以通过 GetDiskFreeSpace 得到扇区大小,并且确保您仅分配和读取这些扇
区大小的数倍。


大型驱动器
另一个与驱动器有够捍娣绞绞墙⒃诿恳唤?
程的基础上。应用程序会试图关闭 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 以后的所有版本中承虿灰恢拢纱耍突岱⑸髦治侍狻T俅吻?
调,如果您需要依靠消息的顺序(尤其是在跨线程的情况下),您不要想当然
地认为消息本身就是进行同步的方式,而要增加自己的同步机制。


多个监视器
从 Windows 98 开始,Windows 具有了处理多个监视器的能力。Windows 2000
是第一个具有这种能力的、基于 Windows NT 的平台。由此出了一个大问题:
您必须确认您的应用程序能正确处理负坐标和超大坐标或表现为超大坐标的情
况。如果设置了多个监视器,而主监视器在副监视器的右侧,则副监视器将完
全处于负坐标区域。如果您的应用程序调出一个应位于副监视器的窗口,而应
用程序希望将窗口最大化,假设应用程序无法正确处理多监视器系统,它会因
当前坐标完全为负而将窗口整个移动到主显示器中。这种情况是不应该发生的
。如果您需要定位窗口,则一定要在多显示器系统中对应用程序进行测试,确
认一定能正确处理这些负坐标。对于dows 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 位组件。您需要对这两个平台的替换问题有清醒的认识。









--------------------------------------------------------------------------------

--


   我想超越这平凡的生活,注定我暂时漂泊!

   我无法停止我内心的狂热,对未来的执着!

※ 来源:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.18.111]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店