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 帶有 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 所使用的堆??臻g要比 Windows NT 4.0 大得多。既然我們使用的是全球范圍內統一的可執行程序,Unicode 所占用的空間就會比以往任何時候都要多,另外,我們還在這里或那里聲明了更多的字符串,結果導致系統需要更多的堆棧。我們發現有些應用程序通過盡可能減少堆??臻g來增強性能。如果要提高運行速度,這無疑是個好主意:很明顯,占用的內存越少,運行的速度就越快。但可惜的是,它們現在確實太小了,由于系統和應用程序一起很快用光了堆??臻g,結果是,應用程序隨即崩潰。 為了確認您的應用程序中是否存在上述問題,需要檢查以下設置:如果您在鏈接行中使用了 /STACK-linker 選項,則檢查該選項;在編譯器中檢查在使用 STACKSIZE 參數或 /F 選項的 STACKSIZE-.def 文件。您需要重新檢查所有這些內容,查看它們是否運行在 Windows 2000 上,并確認堆??臻g不是太小。 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 本身作出改動,也沒有對堆的工作方式進行邏輯上的改動,但是我們對塊的重復利用方式進行了一些微妙的改變,從而使應用程序中的錯誤暴露出來。 簡而言之,我們所作的改動如下所示:以前,當一個塊被釋放出來,它將被列入未應用的空塊列表,位于表的末尾,最后將被篩選出來再一次利用?,F在,我們將緩存最后一次被使用的塊,并把它們放在表的頂端,當您需要另一個塊時,您更可能首先調回這一個塊。如果您需要調回一個塊,又生成了一個空塊,您將會調回剛剛使用過的塊,這樣可以使自己保持在同一頁,并且提高系統整個工作方式的速度。 這就是我們從一開始便在 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 位組件。您需要對這兩個平臺的替換問題有清醒的認識。 ?2000 Microsoft Corporation 版權所有。保留所有權利。使用規定。 |
原文轉自:http://www.anti-gravitydesign.com