在Windows 8.1或Windows 10中正确获取系统版本
acmilan2015/09/17软件综合 IP:四川
很多人都知道一个WinAPI函数:GetVersionEx,这个函数可以获取系统版本的详细信息(信息被放在第一个参数OSVERSIONINFOOSVERSIONINFOEX结构体中),它的Unicode形式如下(WINAPI就是__stdcall的意思):

BOOL WINAPI GetVersionExW(OSVERSIONINFOW* lpVersionInformation);

其中OSVERINFOEX结构体如下(OSVERINFO结构体只到szCSDVersion就没有了):
<code class="lang-cpp">typedef struct _OSVERSIONINFOEXW {
    DWORD dwOSVersionInfoSize;
    DWORD dwMajorVersion;
    DWORD dwMinorVersion;
    DWORD dwBuildNumber;
    DWORD dwPlatformId;
    WCHAR  szCSDVersion[ 128 ];     // Maintenance string for PSS usage
    WORD   wServicePackMajor;
    WORD   wServicePackMinor;
    WORD   wSuiteMask;
    BYTE  wProductType;
    BYTE  wReserved;
} OSVERSIONINFOEXW, *POSVERSIONINFOW, *LPOSVERSIONINFOW, RTL_OSVERSIONINFOW, *PRTL_OSVERSIONINFOW;</code>

但是在Windows 8.1Windows 10中,这个函数不灵了,返回的是如下的信息,即返回系统版本为Windows 8(6.2.9200)

osversion1.png

这是因为微软因为某种原因,在Windows 8.1中废弃了GetVersionEx函数。所有的程序如果使用了GetVersionEx判断系统版本,在Windows 8.1或Windows 10中都会只返回Windows 8的版本信息,而不会返回当前的版本信息。

一个替代的方法是调用VerifyVersionInfo函数,但是这个函数使用比较麻烦。为了方便起见,微软在<VersionHelpers.h>中增加了一个非常方便的辅助函数IsWindowsVersionOrGreater,这个函数只有三个参数——主版本号、次版本号、服务包版本号:

BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor);

但是有时候我们只想返回版本信息,并不想一个个地去验证版本号。在Windows 8及以前版本的操作系统中,kernel32.dll中的GetVersionExW函数会直接调用ntdll.dll中一个名为RtlGetVersion的函数,这个函数在Windows 8.1以上的系统中返回的仍然是正确的信息。它的函数原型如下(NTAPI也是__stdcall的意思):

NTSTATUS NTAPI RtlGetVersion(RTL_OSVERSIONINFOW* lpVersionInformation);

可以看到,这个RtlGetVersion函数跟GetVersionExW的形式基本一致(RTL_OSVERSIONINFOW等于OSVERSIONINFOW),只有返回值不同。NTSTATUS的规则是NTSTATUS>=0成功,NTSTATUS<0失败。

由于微软并没有对ntdll.dll制作相应的XXXXXXXXb导入库文件,这个RtlGetVersion函数并不能直接调用。但是我们可以采取另外的办法。由于kernel32.dll一定会调用ntdll.dll,因此每个程序启动时,ntdll.dll其实已经加载到我们的进程上了,我们不需要再用LoadLibrary加载它,只需要先使用以下方法即可获取ntdll.dll的模块句柄:

HMODULE hNtdll = GetModuleHandleW(L"NTDLL");

然后使用GetProcAdderss(hNtdll, "RtlGetVersion")获取RtlGetVersion函数的指针即可进行调用。为了方便起见,我们可以自己写一个GetVersionEx2函数,用以代替被Windows 8.1废掉的GetVersionEx:
<code class="lang-cpp">BOOL GetVersionEx2(LPOSVERSIONINFOW lpVersionInformation)
{
    HMODULE hNtDll = GetModuleHandleW(L"NTDLL"); // 获取ntdll.dll的句柄
    typedef NTSTATUS (NTAPI*tRtlGetVersion)(PRTL_OSVERSIONINFOW povi); // RtlGetVersion的原型
    tRtlGetVersion pRtlGetVersion = NULL;
    if (hNtDll)
    {
        pRtlGetVersion = (tRtlGetVersion)GetProcAddress(hNtDll, "RtlGetVersion"); // 获取RtlGetVersion地址
    }
    if (pRtlGetVersion)
    {
        return pRtlGetVersion((PRTL_OSVERSIONINFOW)lpVersionInformation) >= 0; // 调用RtlGetVersion
    }
    return FALSE;
}</code>

用这个RtlGetVersion写成的GetVersionEx2函数获取的信息是正确的:

osversion2.png

完整的程序:
<code class="lang-cpp">#include <windows.h>
#include <stdio.h>
#include <atlbase.h>
                       
BOOL GetVersionEx2(LPOSVERSIONINFOW lpVersionInformation)
{
    HMODULE hNtDll = GetModuleHandleW(L"NTDLL"); // 获取ntdll.dll的句柄
    typedef NTSTATUS (NTAPI*tRtlGetVersion)(PRTL_OSVERSIONINFOW povi); // RtlGetVersion的原型
    tRtlGetVersion pRtlGetVersion = NULL;
    if (hNtDll)
    {
        pRtlGetVersion = (tRtlGetVersion)GetProcAddress(hNtDll, "RtlGetVersion"); // 获取RtlGetVersion地址
    }
    if (pRtlGetVersion)
    {
        return pRtlGetVersion((PRTL_OSVERSIONINFOW)lpVersionInformation) >= 0; // 调用RtlGetVersion
    }
    return FALSE;
}
                       
#define NTDLL_RTL_GET_VERSION
                       
int main(int argc, _TCHAR* argv[])
{
    OSVERSIONINFOEXW ovi = {sizeof ovi};
                       
#ifdef NTDLL_RTL_GET_VERSION
    GetVersionEx2((LPOSVERSIONINFOW)&ovi);
    printf("使用NTDLL->RtlGetVersion获取的信息:\n");
#else
    GetVersionExW((LPOSVERSIONINFOW)&ovi);
    printf("使用KERNEL32->GetVersionExW获取的信息:\n");
#endif
                       
    printf("dwMajorVersion: %08x %d\n", ovi.dwMajorVersion, ovi.dwMajorVersion);
    printf("dwMinorVersion: %08x %d\n", ovi.dwMinorVersion, ovi.dwMinorVersion);
    printf("dwBuildNumber: %08x %d\n", ovi.dwBuildNumber, ovi.dwBuildNumber);
    printf("dwPlatformID: %08x %d\n", ovi.dwPlatformId, ovi.dwPlatformId);
    printf("szCSDVersion: %s\n", (char*)CW2A(ovi.szCSDVersion, 1));
    printf("wServicePackMajor: %04x %d\n", ovi.wServicePackMajor, ovi.wServicePackMajor);
    printf("wServicePackMinor: %04x %d\n", ovi.wServicePackMinor, ovi.wServicePackMinor);
    printf("wSuitMask: %04x %d\n", ovi.wSuiteMask, ovi.wSuiteMask);
    printf("wProductType: %02x %d\n", ovi.wProductType, ovi.wProductType);
    printf("wReserved: %02x %d\n", ovi.wReserved, ovi.wReserved);
                       
    return 0;
}</atlbase.h></stdio.h></windows.h></code>

[修改于 9年3个月前 - 2015/09/19 10:37:02]

来自:计算机科学 / 软件综合
10
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
acmilan 作者
9年3个月前 IP:四川
790527
引用 张静茹:
NTDLL.dll是内核吧,R3的程序能调R0的dll?
ntdll.dll并不是内核,而是一个内核调用的stub而已,可工作于R0和R3。NT内核下的Windows程序都会通过kernel32.dll间接链接此库,所以是可以调用的。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年3个月前 IP:四川
790529
这个RtlGetVersion并不是完全独立的。从Windows XP开始,在exe文件的属性里可以设置“兼容模式”。在Windows XP下它不受“兼容模式”影响(用户只能设置GetVersionEx返回的版本),但是在Windows Vista之后它受到“兼容模式”影响(用户可以设置RtlGetVersion返回的版本)。

要想获得不受“兼容模式”干扰的版本信息,可以使用VerifyVersionInfo、IsWindowsVersionOrGreater等函数,也可以使用读取注册表,读取系统DLL版本等方法,也可以使用一个未公开API:RtlGetNtVersionNumbers。

但是作为被废掉的GetVersionEx的等价替代品,RtlGetVersion完全可以满足一般使用。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年3个月前 修改于 9年3个月前 IP:四川
790531
和NTDLL一样,对于KERNEL32等被静态加载的DLL模块,也可以使用GetModuleHandleW获取句柄。如果你想调用某个新版本Windows中才有的存在于kernel32.dll中的WinAPI,又不想降低兼容性的话,就可以通过『GetModuleHandleW-》GetProcAddress-》通过函数指针调用』的方法调用。如以下代码使用kernel32->IsWow64Process判断是否32位程序运行在64位系统上:
<code class="lang-cpp">typedef BOOL (WINAPI *tIsWow64Process) (HANDLE, PBOOL);
              
BOOL bIsWow64 = FALSE;
              
tIsWow64Process pIsWow64Process =
    (tIsWow64Process) GetProcAddress(GetModuleHandleW(L"kernel32"), "IsWow64Process");
              
if (NULL != pIsWow64Process)
{
    if (!pIsWow64Process(GetCurrentProcess(),&bIsWow64))
        printf("IsWow64Process error.\n");
}
              
if (bIsWow64)
{
    printf("IsWow64Process TRUE.\n");
}
else
{
    printf("IsWow64Process FALSE.\n");
}</code>

对于那些并不经常使用的DLL,如dwmapi.dll(Aero)或d2d1.dll(Direct2D)等,有可能DLL并没有被静态加载,或者DLL文件在老系统中可能根本不存在。此时就需要『先LoadLibraryW动态加载这个DLL,然后GetProcAddress获取函数指针,再通过函数指针调用,最后FreeLibrary释放这个DLL』。比如下列代码调用Windows Vista以上版本才具有的TaskDialog函数(函数在comctl32.dll中):
<code class="lang-cpp">//#include <commctrl.h>
                  
// 使用comctl32.dll版本6,这是个预处理指令
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
                    
// TaskDialog函数的原型
typedef HRESULT (WINAPI *tTaskDialog)(HWND hwndParent, HINSTANCE hInstance,
    PCWSTR pszWindowTitle, PCWSTR pszMainInstruction, PCWSTR pszContent,
    TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons, PCWSTR pszIcon, int *pnButton);
                    
// 加载comctl32.dll
HMODULE hcomctl32 = LoadLibraryW(L"comctl32.dll");
                    
// 加载成功
if (hcomctl32 != NULL)
{
    // 获取TaskDialog函数地址
    tTaskDialog pTaskDialog = (tTaskDialog)GetProcAddress(hcomctl32, "TaskDialog");
                    
    // 获取成功,调用TaskDIalog显示对话框
    if (pTaskDialog != NULL)
        pTaskDialog(NULL, NULL, L"任务对话框", L"主指示", L"内容",
                TDCBF_OK_BUTTON|TDCBF_YES_BUTTON, TD_INFORMATION_ICON, NULL);
                    
    // 释放comctl32.dll
    FreeLibrary(hcomctl32);
}</commctrl.h></code>
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年3个月前 IP:四川
790536
RtlGetVersion这个函数是Windows XP以上才支持的。如果想写支持Windows NT 4.0或Windows 2000的程序,可以这样写:
<code class="lang-cpp">BOOL GetVersionEx2(LPOSVERSIONINFOW lpVersionInformation)
{
    HMODULE hNtDll = GetModuleHandleW(L"NTDLL"); // 获取ntdll.dll的句柄
    typedef NTSTATUS (NTAPI*tRtlGetVersion)(PRTL_OSVERSIONINFOW povi); // RtlGetVersion的原型
    tRtlGetVersion pRtlGetVersion = NULL;
    if (hNtDll)
    {
        pRtlGetVersion = (tRtlGetVersion)GetProcAddress(hNtDll, "RtlGetVersion"); // 获取RtlGetVersion地址
    }
    if (pRtlGetVersion)
    {
        return pRtlGetVersion((PRTL_OSVERSIONINFOW)lpVersionInformation) >= 0; // 调用RtlGetVersion
    }
    else
    {
        return GetVersionExW(lpVersionInformation); // Windows XP以下版本
    }
}</code>
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年3个月前 修改于 7年11个月前 IP:四川
790540
一、检测到的系统版本含义

Windows版本
dwMajorVersion
dwMinorVersion
dwBuildNumber
Win9x: byte-byte-word
WinNT: dword
dwPlatformId
Win32s on Windows 3.1
1
任意
任意
0
VER_PLATFORM_WIN32s
Windows 95
4
0
4-0-950
4-0-1111(osr2)
4-3-1214(usb)
1
VER_PLATFORM_WIN32_WINDOWS
Windows 98
4
10
4-10-1998
4-10-2222(se)
1
Windows Me
4
90
4-90-3000
1
Windows NT 3.1
3
10
528
[不支持GetVersionEx]
Windows NT 3.5
3
50
807
2
VER_PLATFORM_WIN32_NT
Windows NT 3.51
3
51
1057
2
Windows NT 4.0
4
0
1381
2
Windows 2000
5
0
2195
2
Windows XP
5
1
2600
2
Windows Server 2003
Windows XP x64
5
2
3790
2
Windows Vista
Windows Server 2008
6
0
6000/6001/6002
2
Windows 7
Windows Server 2008 R2
6
1
7600/7601
2
Windows 8
Windows Server 2012
6
2
9200
2
Windows 8.1
WindowsServer 2012 R2
6
3
9600
2
Windows 10 TH1
10
0
10240
2

szCSDVersion含有一个字符串,指示系统更新或服务包版本,比如" B"或"Service Pack 1"。
Win32s on Windows 3.1有可能是"Retail version"等;
Windows 95/98/Me有可能是:"a"、" b"、" A"、" B"、" C"、"A"、"B"等(可在注册表中设置);
Windows NT/2000/XP/2003/Vista/7有可能是:"Service Pack n";
Windows 8以后的操作系统因为没有发布服务包,通常为空。

二、以下字段可用于Windows NT SP6或者更高版本

wServicePackMajorwServicePackMinor指示安装的服务包版本,例如:
Service Pack 1的wServicePackMajor=1,wServicePackMinor=0
Service Pack 2的wServicePackMajor=2,wServicePackMinor=0
Service Pack 3的wServicePackMajor=3,wServicePackMinor=0
依次类推。
wSuiteMask(已安装产品套装)置位取值,这一位主要用于特殊产品,具体用法参阅msdn。
wProductType(产品类别)的取值:
VER_NT_WORKSTATION = 1 (工作站,如Windows 2000 Professional/XP/Vista/7/8/8.1/10)
VER_NT_DOMAIN_CONTROLLER = 2 (域控制器,开启了域控制器功能的Windows Server)
VER_NT_SERVER = 3 (服务器,未开启域控制器功能的Windows Server)
wReserved保留不用。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年2个月前 修改于 9年1个月前 IP:四川
795490
根据最新消息,windows 10 版本 1511(th2_release)版本号为10.0.10586。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年10个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}