介紹
用戶和開發人員面對的大多數普通應用程序的兼容性問題,就是應用程序檢 測操作系統版本後運行失敗。當版本檢測出現錯誤的時候,很多應用程序都會出錯。用戶可 能會有“靜默失敗”的體驗,也就是一個應用程序加載失敗並且什麼都沒有發生 。又或者用戶可能會看到一個帶有標示的對話框,寫明了“你必須運行在 Microsoft Windows XP或更新版本”,但實際上計算機已經安裝了Windows 7 。很多由於不嚴謹的 版本檢測導致的結果,能夠給用戶帶來很多的不便。
由於版本檢測造成的應用程序運行失敗,通常是兩個原因:
• 在檢測版本的 代碼中的缺陷(錯誤)。比如次版本號降低,主版本提高(例如,版本號從5.1變為6.0)或 者一些期望的service pack (SP)沒有安裝,又或者你已經安裝了更新的額操作系統(比如, 版本從Windows XP SP 2 變為 Windows Vista SP 1),都可能造成應用程序運行失敗。
• 故意的阻斷,應用程序的開發人員會阻止應用程序運行在沒有經過測試的操作系 統版本上(我們的建議是,不要阻止應用程序運行在未來的操作系統中)。
當一個應 用程序以“不兼容“(由於不嚴謹的版本檢測)的Windows版本運行,那麼它通常 會提示一個錯誤信息,但是它也有可能靜默退出或者有其他的行為。通常,如果我們使用版 本檢測的解決方案,那麼應用程序將能夠很好的運行。最終用戶和IT專業人員就能使用一個 簡單的修復,來讓應用程序知道它現在正運行在一個早期的Windows版本上。
下面的章節提供了一些關於如何解決版本檢測的兼容性問題的信息,同時也提供了如何檢 測操作系統版本的信息。我們同時也給出最好最全面的建議:不用檢測操作系統的版本,而 只需要檢測操作系統的功能。
解決問題
Windows給我們提供了兩種結構來解決 版本檢測的問題:
• 兼容性模式:它是專門為最終用戶設計的,兼容模式是解 決兼容性問題的簡單方法。當它被啟用的時候,將修復一些列的兼容性問題,並且為早期 Windows版本所開發的應用程序提供一個更加兼容的運行時環境。這些修復的問題中的其中一 個就是“版本欺騙“,它將使函數返回一個用戶在屬性對話框中的兼容性標簽中 選中的操作系統版本,而不是計算機的實際Windows版本。
• 應用程序兼容性工 具 (ACT):為IT專業人員和開發人員提供的應用程序兼容性工具的集合,ACT提供了檢測和調 試大量兼容性問題的方法,開發人員可以用來修復應用程序,同時,ACT還提供了通過從列表 中使用兼容性選項來解決兼容性問題的方法,包括“版本欺騙“。ACT提供了一套 能夠解決細節問題的工具。
兼容模式
要啟用兼容模式:
1.右鍵點擊可 執行文件或者可執行文件的快捷方式。
2.點擊屬性。
3.點擊兼容性標簽。
4.選中以兼容模式運行這個應用程序:並且選擇你希望應用程序運行的操作系統。一 些應用程序可能包含多個可執行文件。你可能需要為每一個文件都執行這樣的操作。
5.點擊OK來關閉該對話框。
6.運行應用程序。
注意 : 兼容模式一般不會對通過Environment.OSVersion函數,或者通過P/Invoke調用 Win32函數(例如,GetVersionEx)的檢測版本的托管代碼應用程序產生影響。但是,如果應 用程序混合了托管代碼和本地代碼,那麼這個方法仍然是有幫助的。如果這個應用程序檢測 版本是通過本地調用來實現的,那麼這個方法將被使用。
應用程序兼容性工具
在安裝了ACT之後,在開始菜單中運行兼容性管理員:
1.查看左邊的樹形菜單。如果這裡沒有“自定義數據庫“節點,那麼點擊工具欄中的New 。
2.右鍵點擊 New Database
3.點擊 Rename ,並且給兼容性數據庫起一個名稱。
4.右鍵點擊你剛才重命名的數據庫,指向Create New,然後點擊Application Fix。 Create new Application Fix對話框將彈出:
5.填充一些細節信息。
6.點擊 Next.
7.在Operating System Modes中,選擇None。
8.在 Select additional compatibility modes:中,選擇 WinXPSP2VersionLie.
9.點擊 Next.
10.如果你的應用程序需要其他的東西,那麼也在這裡進行選擇。
11.點擊 Next。
12.選擇Windows需要為可執行程序定義的條件。
13.點擊 Finish.
14.點擊工具欄中的Save來將兼容性數據庫保存到一個文件中(以.sdb為擴展名的文件) 。你可以使用Windows SDBinst工具將這個文件安裝到目標計算機上。
15.Windows可以為很多程序都使用這種包含多種修改的兼容性數據庫;你可以通過展開 System Database/Applications節點來查看這些。點擊子節點就可以看到其對應用程序所做 的修改。
解決方案
更好的版本檢測
通過識別當前的操作系統版本來決定這個操作系統所擁有的功能,並不是一個最佳的方法 。但是,如果你不能將你的應用程序設計成能夠檢測指定的功能,那麼唯一的確定兼容性的 方式,就是通過版本檢測,那麼,請參考下面的方法。
對於本地應用程序,你可能需要確保你的應用程序邏輯能夠在更新的Windows版本上運行 。當版本變更的時候請不要阻止應用程序運行!下面就是一個使用了GetVersionEx 的Win32 代碼示例。如果主版本號大於5(Windows Vista, Windows Server 2008 R2 和 Windows 7) ,那麼檢測就能夠通過。如果等於5,那麼次版本號應該是1或者更大(Windows XP 或者 Windows Server 2003)。
C++
#include <windows.h>
#include <stdio.h>
void main()
{
OSVERSIONINFO osvi;
BOOL bIsWindowsXPorLater;
ZeroMemory(&osvi, sizeof (OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
GetVersionEx(&osvi);
bIsWindowsXPorLater =
( (osvi.dwMajorVersion > 5) ||
( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) ));
if(bIsWindowsXPorLater)
printf("The system meets the requirements.\n");
else printf("The system does not meet the requirements.\n");
}
下面的代碼示例是使用了VerifyVersionInfo來檢測操作系統的版本是否滿足最低需求 (Windows XP SP2):
C++
#include <windows.h>
BOOL Is_WinXP_SP2_or_Later ()
{
OSVERSIONINFOEX osvi;
DWORDLONG dwlConditionMask = 0;
int op=VER_GREATER_EQUAL;
// Initialize the OSVERSIONINFOEX structure.
ZeroMemory(&osvi, sizeof (OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEX);
osvi.dwMajorVersion = 5;
osvi.dwMinorVersion = 1;
osvi.wServicePackMajor = 2;
osvi.wServicePackMinor = 0;
// Initialize the condition mask.
VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, op );
VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, op );
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, op );
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMINOR, op );
// Perform the test.
return VerifyVersionInfo(
&osvi,
VER_MAJORVERSION | VER_MINORVERSION |
VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
dwlConditionMask);
}
對於.NET Framework開發人員,使用==, !=, <=, <, >, >=操作符來對 Environment.OSVersion.Version返回的Version對象進行操作:
C#
// This code checks if the OS is at least Windows XP
if (Environment.OSVersion.Version < new Version(5, 1))
{
MessageBox.Show("Windows XP or later required.",
"Incompatible Operating System", MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
檢測功能
之前提到的,檢測操作系統的 版本來確定操作系統所能提供的功能,不是一個最佳的方法。這是因為操作系統可能會在可 再發行DLL中增加新的功能。與其使用 GetVersionEx來決定操作系統的平台或者版本號,不 如檢測操作系統本身所提供的功能更為有效。例如,我們准備使用Direct2D 和 DirectWrite 這些API和在Windows Vista中所提供的Ribbon的API,那麼在使用這些API時不需要阻斷你的 應用程序。
如果可能,你的應用程序應該在沒有這些功能的依然能夠正常運行,只不 過是減少一些功能或者性能。
對於Win32,使用下面的技術:
• 使用 LoadLibrary()來加載一個還沒有加載進你的應用程序的庫。如果你對已經加載的DLL文件( 比如,kernel32.dll)中的新功能感興趣,那麼可以調用GetModuleHandle()來獲取模塊的句 柄。如果這些函數都返回空值,那麼這將引起一個錯誤。
• 使用GetProcAddress()來獲取一個函數指針。如果GetProcAddress()返回一個空 值,那麼這個函數可能將會退出。我們需要將函數的指針設置成適當的原型。有一些函數, 盡管他們退出了,但也有可能實際返回一個“未完成“的錯誤。同樣我們也需要 檢查這些錯誤。
下面的代碼示例就展示了這個技術:
C++
// define function pointer type
typedef BOOL (WINAPI *SetWaitableTimerExProc)(
__in HANDLE hTimer,
__in const LARGE_INTEGER *lpDueTime,
__in LONG lPeriod,
__in PTIMERAPCROUTINE pfnCompletionRoutine,
__in LPVOID lpArgToCompletionRoutine,
__in PREASON_CONTEXT WakeContext,
__in ULONG TolerableDelay
);
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = 0;
nt period = 1000;
unsigned int tolerance = 1000;
HANDLE hTimer = // Get timer handle
REASON_CONTEXT reasonContext = {0};
reasonContext.Version = 0;
reasonContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
reasonContext.Reason.SimpleReasonString = L"MyTimer";
// Get module handle to a module which is already loaded
HMODULE hKernel32Module = GetModuleHandle(_T("kernel32.dll"));
if (hKernel32Module == NULL)
return FALSE;
// Get Address of function
SetWaitableTimerExProc pFnSetWaitableTimerEx =
(SetWaitableTimerExProc) ::GetProcAddress(hKernel32Module,
"SetWaitableTimerEx");
// Check if the function exists
if (pFnSetWaitableTimerEx == NULL)
return FALSE;
// Call function
if (! pFnSetWaitableTimerEx(hTimer, &liDueTime, period, NULL, NULL,
&reasonContext, tolerance)
{ // handle error }
二者擇其一,你可以選擇使用DLL延遲加載,並且在 __try...__異常阻斷中調用函數(了解更多信息,查看Linker Support for Delay-Loaded DLLs)。
對於COM API,我們可以操作通過CoCreateInstance 和 QueryInterface 所 返回的錯誤。
通過P/Invoke調用Win32 API的.NET 框架應用程序,我們需要去管理 EntryPointNotFoundException 和 DllNotFoundException兩個異常。
工具列表
Microsoft應用程序兼容性工具
附加資源
• 應用程序兼容性手冊 : http://msdn.microsoft.com/en-us/library/bb963893.aspx
• 版本欺騙和 托管應用程序:http://blogs.msdn.com/cjacks/archive/2007/09/10/version-lie-shims- and-managed-code-on-windows-vista.aspx