在Windows系统开发或运维脚本编写中,准确获取当前操作系统的版本信息是一个常见需求。然而,许多开发者会发现,使用常见的GetVersionEx函数或直接查询注册表ProductName得到的结果并不准确。本文将介绍一种通过底层ntdll.dll调用获取真实版本信息的方法,并提供完整的C++实现代码。
为何常规方法会失效?
在尝试获取Windows版本时,开发者常会遇到以下痛点:
注册表查询不准确
通过读取注册表路径 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion 下的 ProductName 值,得到的信息可能与实际系统不符。例如:
- Windows 11 可能显示为 “Windows 10”
- Windows 8 早期版本可能显示为 “Windows 6.2”
传统API被废弃
GetVersionEx API 自 Windows 8.1 起,其行为发生了变化。为了促进应用程序兼容性,未正确声明目标操作系统版本的应用程序将收到兼容性版本号(例如,在未清单的应用程序中,Windows 8.1 和 10 可能返回 Windows 8 的版本号 6.2)。微软已不鼓励使用此函数。
构建号(Build Number)的混淆
虽然注册表中也存在 CurrentBuildNumber,但其信息有时也不可靠。为了获得最核心、最准确的构建版本号,最佳实践是直接与系统底层交互。
解决方案:调用 ntdll.dll 中的 RtlGetVersion
最可靠的方法是动态调用位于 ntdll.dll 中的 RtlGetVersion 函数。这是GetVersionEx等API的底层实现,能够绕过应用程序兼容性劫持,返回操作系统真实的版本信息。
兼容性说明:RtlGetVersion 函数最低支持 Windows 2000 Professional 操作系统。
核心函数实现
下面的C++函数演示了如何安全地调用 RtlGetVersion 来获取 RTL_OSVERSIONINFOEXW 结构体,其中包含了系统的主版本、次版本和构建号。
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOEXW);
RTL_OSVERSIONINFOEXW GetRealOSVersion() {
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
if (hMod) {
RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
if (fxPtr != nullptr) {
RTL_OSVERSIONINFOEXW rovi = { 0 };
rovi.dwOSVersionInfoSize = sizeof(rovi);
if (STATUS_SUCCESS == fxPtr(&rovi)) {
return rovi;
}
}
}
RTL_OSVERSIONINFOEXW rovi = { 0 };
return rovi;
}
该函数首先获取 ntdll.dll 的模块句柄,然后找到 RtlGetVersion 函数的地址并调用它。通过这种方式,我们可以确保获取到的是操作系统内核报告的真实数据,这对于需要精确版本判定的系统编程场景至关重要。
获取Windows系统信息的完整实践
仅获取构建号还不够,一个完整的版本信息工具通常需要整合多种数据源。以下程序综合了注册表查询、传统API和 RtlGetVersion 方法,并提供了一个将构建号映射到已知Windows版本名称的查表功能。
完整代码解析
程序主要包含以下几个功能模块:
- 注册表信息读取:获取
DisplayVersion (如22H2)、ProductName、CurrentBuildNumber 和 UBR (更新构建版本)。
- 传统API调用:演示
GetVersionEx 的调用(结果可能受清单影响)。
- 真实版本获取:使用上述
GetRealOSVersion 函数。
- 版本号映射:通过一个包含从Windows 3.1到Windows 11 24H2的构建号映射表,将数字构建号转换为可读的系统名称。
#include <iostream>
#include <string>
#include <windows.h>
#include <map>
typedef LONG NTSTATUS, * PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOEXW);
// ... (此处省略上文已列出的 GetRealOSVersion() 函数及注册表操作辅助函数)
class WindowsVersions {
public:
static std::map<int, std::string> getWindowsVersions() {
std::map<int, std::string> windowsVersions;
// Windows NT 系列
windowsVersions[2195] = "Windows 2000 Professional";
windowsVersions[2600] = "Windows XP";
windowsVersions[3790] = "Windows XP Professional x64 Edition/Windows Server 2003";
windowsVersions[6000] = "Windows Vista/Windows Server 2008";
windowsVersions[7601] = "Windows 7, Service Pack 1";
windowsVersions[9200] = "Windows 8/Windows Server 2012";
windowsVersions[9600] = "Windows 8.1";
windowsVersions[10240] = "Windows 10 version 1507";
windowsVersions[19045] = "Windows 10 version 22H2";
windowsVersions[22000] = "Windows 11 21H2";
windowsVersions[22621] = "Windows 11 22H2";
windowsVersions[26100] = "Windows 11 24H2";
// 可在此处继续添加更新版本的映射
return windowsVersions;
}
};
int main() {
HKEY hKey;
// 打开注册表项
if (!OpenRegistryKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey)) {
std::cerr << "无法打开注册表项。" << std::endl;
return 1;
}
// 1. 读取注册表中的各种信息
char szDisplayVersion[256];
if (GetDisplayVersion(hKey, szDisplayVersion, sizeof(szDisplayVersion))) {
printf("DisplayVersion: %s\n", szDisplayVersion);
}
char szProductName[256];
if (GetProductName(hKey, szProductName, sizeof(szProductName))) {
printf("ProductName: %s\n", szProductName);
}
char szCurrentBuildNumber[256];
if (GetCurrentBuildNumber(hKey, szCurrentBuildNumber, sizeof(szCurrentBuildNumber))) {
printf("CurrentBuildNumber: %s\n", szCurrentBuildNumber);
}
DWORD ubr = 0;
if (GetUBR(hKey, ubr)) {
printf("UBR: %d\n", ubr); // UBR 代表本次构建内的更新版本,用于区分安全更新
}
// 2. 调用可能被劫持的 GetVersionEx
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
printf("\nGetVersionEx 获取的信息 (可能不准确):\n");
printf(" 版本: %d.%d (Build: %d, CSD: %s)\n", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber, osvi.szCSDVersion);
// 3. 调用底层的 RtlGetVersion 获取真实版本
printf("\nRtlGetVersion 获取的真实信息:\n");
RTL_OSVERSIONINFOEXW osInfo = GetRealOSVersion();
if (osInfo.dwOSVersionInfoSize != 0) {
if (wcscmp(osInfo.szCSDVersion, L"") == 0)
printf(" 核心版本: %d.%d (Build: %d)\n", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber);
else
printf(" 核心版本: %d.%d (Build: %d, CSD: %ws)\n", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.szCSDVersion);
// 4. 通过构建号查表确定系统名称
int currentBuildNumber = osInfo.dwBuildNumber;
std::map<int, std::string> windowsVersions = WindowsVersions::getWindowsVersions();
auto it = windowsVersions.find(currentBuildNumber);
if (it != windowsVersions.end()) {
printf(" 对应系统: %s\n", it->second.c_str());
} else {
printf(" 构建号 %d 未在已知版本列表中,可能为更新版本。\n", currentBuildNumber);
}
}
// 关闭注册表项
RegCloseKey(hKey);
getchar();
return 0;
}
程序输出示例
编译并运行上述程序,你可能会看到类似下面的输出,它清晰地对比了不同方法得到的结果:
DisplayVersion: 22H2
ProductName: Windows 10 Pro
CurrentBuildNumber: 19045
UBR: 3086
GetVersionEx 获取的信息 (可能不准确):
版本: 10.0 (Build: 19045, CSD: )
RtlGetVersion 获取的真实信息:
核心版本: 10.0 (Build: 19045)
对应系统: Windows 10 version 22H2
在这个例子中,尽管注册表ProductName显示为“Windows 10 Pro”,但通过构建号19045查表,我们准确地确定了系统是“Windows 10 version 22H2”。GetVersionEx和RtlGetVersion在本例中结果一致,但这取决于应用程序的清单配置。
![]https://static1.yunpan.plus/attachment/d94c275a2646.png)
![]https://static1.yunpan.plus/attachment/d94c275a2646.png)
对比不同方法获取的Windows版本信息(示意图)
总结与建议
在需要精确判断Windows版本的场景下(例如软件兼容性检查、系统运维脚本或驱动开发),建议优先使用 RtlGetVersion 方法获取构建号,并结合构建号映射表来确定具体版本。
- 注册表
ProductName:适用于向用户展示,但不适合用于逻辑判断。
GetVersionEx:已过时,其行为受应用程序清单影响,结果不可靠。
RtlGetVersion + 构建号映射:是当前获取真实、准确系统版本信息的最可靠方法。
你可以根据上述提供的完整代码,轻松地将其集成到自己的项目或工具中,实现精准的Windows版本检测。