史萊姆論壇

史萊姆論壇 (http://forum.slime.com.tw/)
-   作業系統操作技術文件 (http://forum.slime.com.tw/f128.html)
-   -   Microsoft Windows 2000 應用程式相容性 (http://forum.slime.com.tw/thread59054.html)

psac 2003-08-11 07:43 PM

Microsoft Windows 2000 應用程式相容性
 
Microsoft Windows 2000 應用程式相容性



作者:Kyle Marsh
Microsoft Corporation

摘要:討論使應用程式在 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 (RegQuery類型Ex(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 重轉發IP。由此管理員可以將 DLL 的載入程序重轉發IP到某一位置,並由本機目錄載入 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 所使用的堆棧空間要比 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 位元件。您需要對這兩個平台的替換問題有清醒的認識。
?2000 Microsoft Corporation 版權所有。保留所有權利。使用規定。

linlili 2003-08-12 12:02 AM

感謝分享


所有時間均為台北時間。現在的時間是 10:44 AM

Powered by vBulletin® 版本 3.6.8
版權所有 ©2000 - 2024, Jelsoft Enterprises Ltd.

『服務條款』

* 有問題不知道該怎麼解決嗎?請聯絡本站的系統管理員 *


SEO by vBSEO 3.6.1