在Windows操作系统中,正确地检测当前的操作系统版本对于软件开发者来说至关重要。本文将介绍几种在不同Windows版本中检测操作系统版本的方法,包括使用manifest文件和内核函数RtlGetVersion。
Windows 11的内核版本号实际上是10.0,这意味着从内核的角度来看,Windows 10和11在很大程度上是相同的。Windows 11的命名可能是出于市场营销的考虑。
许多为Windows XP编写的程序在Vista上无法运行,因为Vista的版本号是6.0,而XP的版本号检查会失败,因为0既不大于也不等于1。Windows 7的版本号是6.1,所以它不受此影响。
C++
if (version.Major >= 5 && version.Minor >= 1) {
// WinXP或更高版本:可以继续!
}
修正后的检查如下,但当时已经太晚,无法修复Vista时代的Windows XP应用程序。
C++
if (version.Major > 5 || (version.Major == 5 && version.Minor >= 1)) {
// WinXP或更高版本
}
为了防止历史重演,微软为.NET Framework 4.8和C++设计了另一种基于XML格式manifest文件的Windows版本查询方法。manifest文件嵌入在最终的可执行文件中。.NET6不需要这个manifest文件来获取正确的Windows版本。遗憾的是,.NET Framework 4.8和C++项目似乎被微软抛弃了,转而支持.NET6。
在本节中,将使用下面的代码来探索XML manifest方法来查询Windows版本。如果是一名C++程序员,请点击C++标签查看C++源代码。
C#
using System;
var info = Environment.OSVersion.Version;
Console.WriteLine("Windows Version: {0}.{1}.{2}", info.Major, info.Minor, info.Build);
C++
#include
OSVERSIONINFOW osv;
osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
if (GetVersionExW(&osv)) {
std::cout << "Windows Version: " << osv.dwMajorVersion << "." << osv.dwMinorVersion << "." << osv.dwBuildNumber << "\n";
}
以上代码在Windows 11机器上输出的版本是6.2,这是Windows 8.0的版本,显然这是错误的。让添加一个manifest文件到项目中来修复它。
在Visual Studio解决方案中,右键点击C#项目名称,在解决方案资源管理器中选择“添加”->“新建项”。弹出一个选择对话框,点击“常规”并选择“应用程序清单文件(仅限Windows)”,并输入文件名app.manifest。
对于Visual C++,微软没有提供添加manifest文件的方法。将这样做:从C#项目中复制app.manifest到Visual C++项目文件夹,并将其重命名为manifest.xml,然后按照以下步骤通知Visual C++这个manifest.xml。右键点击C++项目,在解决方案资源管理器中选择“属性”,在弹出的“属性对话框”中点击“清单工具->输入和输出->附加清单文件”,并添加"manifest.xml"。
在清单文件中,将取消注释Windows 8.1的supportedOS,通过删除周围的<!--和-->来查看其效果。
XML
现在代码输出6.3,这是Windows 8.1的版本,仍然是错误的。请记住,机器上安装的是Windows 11。
接下来,将取消注释Windows 10的supportedOS来看看它的效果。
Windows Version: 10.0.22000
现在这是正确的。Windows 10和11共享相同的主版本号和次版本号,Windows 11通过构建号22000来区分。清单方法确保应用程序永远不会获得比其清单中指定的版本更高的版本。接下来,将使用Windows内核函数RtlGetVersion来绕过清单文件,始终报告当前的Windows版本,而不受清单文件的存在/不存在的影响。
在GetVersion中,将通过P/Invoke调用RtlGetVersion。对于C++程序员,请点击查看C++源代码。
C#
public static bool GetVersion(out VersionInfo info) {
info.Major = 0;
info.Minor = 0;
info.BuildNum = 0;
OSVERSIONINFOEXW osv = new OSVERSIONINFOEXW();
osv.dwOSVersionInfoSize = 284;
if (RtlGetVersion(out osv) == 0) {
info.Major = osv.dwMajorVersion;
info.Minor = osv.dwMinorVersion;
info.BuildNum = osv.dwBuildNumber;
return true;
}
return false;
}
C++
bool WinVersion::GetVersion(VersionInfo& info) {
OSVERSIONINFOEXW osv;
osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
if (RtlGetVersion(&osv) == 0) {
info.Major = osv.dwMajorVersion;
info.Minor = osv.dwMinorVersion;
info.BuildNum = osv.dwBuildNumber;
return true;
}
return false;
}
这是GetVersion()的使用方式。
C#
if (WinVersion.GetVersion(out var info)) {
Console.WriteLine("Windows Version: {0}.{1}.{2}", info.Major, info.Minor, info.BuildNum);
Console.ReadKey();
}
C++
VersionInfo info;
if (WinVersion::GetVersion(info)) {
std::cout << "Windows Version : " << info.Major << "." << info.Minor << "." << info.BuildNum << "\n";
}
这是Windows 11的版本。
Windows Version: 10.0.22000
在Pavel Yosifovich的《Windows Native API Programming》一书中提到了一种通过KUSER_SHARED_DATA结构体获取Windows版本的方法,该结构体位于每个进程的0x7ffe0000地址。有关KUSER_SHARED_DATA的更多信息,请阅读此MSDN链接。
KUSER_SHARED_DATA位于<ntddk.h>中,该文件随Windows驱动开发工具包(DDK)一起提供。必须安装DDK才能编译下面的代码片段。
C++
// Requires installation of the Windows Driver Development Kit (ddk)
#include
#include
int main() {
auto data = (KUSER_SHARED_DATA*)0x7ffe0000;
printf("Version: %d.%d.%d\n", data->NtMajorVersion, data->NtMinorVersion, data->NtBuildNumber);
return 0;
}
由于没有在系统上安装DDK。幸运的是,在Pavel Yosifovich的《Windows 10 System Programming, Part 1》一书中找到了这段代码,它不依赖于KUSER_SHARED_DATA,而是依赖于其结构体成员的地址偏移。它在Visual C++ 2022上编译并正常工作。
C++
#define WIN32_LEAN_AND_MEAN
#include
#include
int main() {
auto sharedUserData = (BYTE*)0x7FFE0000;
printf("Version: %d.%d.%d\n", *(ULONG*)(sharedUserData + 0x26c), // major version offset
*(ULONG*)(sharedUserData + 0x270), // minor version offset
*(ULONG*)(sharedUserData + 0x260)); // build number offset
return 0;
}
Version: 10.0.22621