Microsoft Windows 2000 應用程式相容性 1 (轉)

worldblog發表於2007-12-04
Microsoft Windows 2000 應用程式相容性 1 (轉)[@more@]

 

2000 應用相容性
Kyle Marsh
Microsoft Corporation

1999 年 11 月

摘要:討論使應用程式在 Microsoft(R) Windows(R) 2000 上存在不相容性的幾個問題。其中有以下幾部分:

介紹
設定和問題
相容性問題
應用程式穩定性問題
Windows 平臺之間的差異

介紹
幾個月來,我一直從事一項任務,即找出 Windows 2000 操作中的應用程式相容性問題。在這裡我真正要討論的是,造成應用程式與 Windows 2000 不相容的原因。沒有人真正關心使應用程式相容的原因。

我一直在與 Windows 2000 測試組合作,他們在過去的幾個月中已測試了數百個應用程式。我們已將應用程式在 Windows 2000 上正常或不正常執行的原因進行書面論述。我們發現的問題可以歸為四類:

無法在 Windows 2000 上安裝的應用程式。 這是迄今我們發現的最大問題。應用程式在 Windows 2000 上安裝的方式並無甚特殊之處;問題是這些應用程式不讓自己安裝到這一新版本的中。


我們對作業系統所做的、影響應用程式執行的更改。每當 Microsoft (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,因為沒有新的 ,我不能正常執行,所以我可能無法安裝。如果系統是版本 4,我可以進行安裝和設定”。問題出在如果系統是版本 5,這一“if”語句就沒有了下文。因為版本號是 5.0,這些應用程式由於自身原因而無法安裝,所以我們發現了一系列這樣那樣的問題。

if (osvi.dwMajorVersion == 3)
  {
  // 請這樣做
  }
else if (osvi.dwMajorVersion == 4)
  {
  // 請那樣做
  }

測試組繼續尋找解決方案,並矇蔽了許多此類應用程式。在早期的編譯中,我們能夠採取措施改變 GetVersionEx 的返回值。我們可以改變其返回值,欺騙應用程式,告訴它版本號就是 4.0,然後程式就能夠繼續安裝,並正常執行。但有部分應用程式的設計思想就是不能安裝在 Windows 2000 上。對於掃描程式或其他低階實用程式來說,受限於某一作業系統版本是可以理解的。不過,這些應用程式會顯示訊息來說明這一點。我們查詢的是那些不能安裝或無法正常執行、又根本沒有通知使用者的應用程式。

怎樣才能正確地檢查版本號?在 Windows 2000 中我們將新增一個新的 : 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(D 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.dwPlatfod == VER_PLATFORM__NT )
  {
  HKEY  hKey;
  DWORD  dwCSDVersion;
  DWORD  dwSize;
  BOOL  fMeetsSPRequirement = FALSE;
 
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  "SystemCurrentControlSetControlWindows", 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,您只需檢查以下注冊鍵值,其中有一個數字是服務包號。提取這一數字,並進行一個“等於”或“大於”比較即可:

HCLMSystemCurrentControlSetControlWindowsCSDVersion

如果您執行的是 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 、中的所有 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 是 CGetNextProtectedFile。可用這一 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 = WCharToMultiByte(CP_ACP,NULL,pfd.FileName, wcslen(pfd.FileName),
  szFileName,260,NULL,NULL);
  szFileName[iLen] = ';
  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]  = "";
  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  = " a File";
  OpenFileName.Flags  = OFN_FILEMUSTEXIST;

  if (g_pfnSHGetFolderPath != NULL )
  g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);
  else
  szSystem32[0] = ';
  OpenFileName.lpstrInitialDir  = szSystem32;
  // 呼叫公共對話函式。
  if (GetOpenFileName(&OpenFileName))
  {
  // 檢查檔案
  WCHAR wzFileName[260];
  int iLen;
  iLen = MultiByteToWideChar(CP_ACP,NULL,szFile, strlen(szFile), wzFileName, 260);
  wzFileName[iLen] = ';
  if (g_pfnSfcIsFileProtected(NULL, wzFileName) == TRUE )
  {
  MessageBox(hWnd,"Is Protected",

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988093/,如需轉載,請註明出處,否則將追究法律責任。

相關文章