v:* { }
o:* { }
w:* { }
.shape { }p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; text-align: justify; font-size: 10.5pt; font-family: "Calibri", "sans-serif" }
h1 { margin-right: 0cm; margin-left: 0cm; font-size: 24.0pt; font-family: 宋体 }
p.MsoHeader,li.MsoHeader,div.MsoHeader { margin: 0cm; margin-bottom: .0001pt; text-align: center; border: none; padding: 0cm; font-size: 9.0pt; font-family: "Calibri", "sans-serif" }
p.MsoFooter,li.MsoFooter,div.MsoFooter { margin: 0cm; margin-bottom: .0001pt; font-size: 9.0pt; font-family: "Calibri", "sans-serif" }
a:link,span.MsoHyperlink { color: blue; text-decoration: none }
a:visited,span.MsoHyperlinkFollowed { color: purple; text-decoration: none }
pre { margin: 0cm; margin-bottom: .0001pt; font-size: 12.0pt; font-family: 宋体 }
p.MsoAcetate,li.MsoAcetate,div.MsoAcetate { margin: 0cm; margin-bottom: .0001pt; text-align: justify; font-size: 9.0pt; font-family: "Calibri", "sans-serif" }
span.Char { }
span.Char0 { }
span.Char1 { }
span.1Char
{ font-family: 宋体; font-weight: bold }
span.HTMLChar { font-family: 宋体 }
span.SpellE { }
.MsoChpDefault { }
div.WordSection1 { }table.MsoNormalTable { font-size: 10.5pt; font-family: "Calibri", "sans-serif" }

Windows系统版本判定那些事儿

前言

本文并不是讨论Windows操作系统的版本来历和特点,也不是讨论为什么没有Win9,而是从程序员角度讨论下Windows获取系统版本的方法和遇到的一些问题。在Win8和Win10出来之后,在获取系统版本时,可能很多人都碰到了类似的问题,为什么以前工作得很好的API,突然开始说谎了?

我们一般怎么获取系统版本

我想用的最多的可能就是这两个API了吧。

DWORD WINAPI GetVersion (VOID);

BOOL WINAPI GetVersionExW(__inout LPOSVERSIONINFOW lpVersionInformation);

其实GetVersion和GetVersionExW的实现是类似的,内部都是调用的NtCurrentPeb这个函数,还有一个GetVersionExA内部则是调用的GetVersionExW来实现。

GetVersionExW大概是这么实现的(这只是Windows2000的源码,后面的新系统,OSVERSIONINFOW这个结构多了几倍的成员)。

WINBASEAPI BOOL WINAPI GetVersionExW(

LPOSVERSIONINFOW lpVersionInformation)

{

PPEB Peb;

if (lpVersionInformation->dwOSVersionInfoSize != sizeof( *lpVersionInformation )) {

SetLastError( ERROR_INSUFFICIENT_BUFFER );

return FALSE;

}

Peb = NtCurrentPeb();

lpVersionInformation->dwMajorVersion = Peb->OSMajorVersion;

lpVersionInformation->dwMinorVersion = Peb->OSMinorVersion;

lpVersionInformation->dwBuildNumber  =Peb->OSBuildNumber;

lpVersionInformation->dwPlatformId  = Peb->OSPlatformId;

wcscpy(lpVersionInformation->szCSDVersion,BaseCSDVersion );

return TRUE;

}

其中BaseCSDVersion是个全局变量,存放的是系统SP的字符串信息,在DLL初始化的时候就已经赋值了,由BaseDllInitialize来初始化。重点看下NtCurrentPeb这个函数,其实很显然,GetVersionExW就是从PEB里面去拷贝版本信息。NtCurrentPeb是一个调用比较频繁的函数,它返回当前进程的PEB结构地址,也就是通过fs寄存器去定位PEB,然后在GetVersionExW里面把PEB里面的系统版本信息拷贝给GetVersionExW的传出参数,也就是上面的OSMajorVersion等成员。

现在为什么不行了

但是从Windows8.1出来之后,GetVersionExW这个API被微软明文给废弃了,这个坑下得可够大的(参考[1])。也就是说从Windows8.1开始之后(包括Windows10),这个API常规情况下就是返回6.2了。

“In Windows 8.1, the GetVersion(Ex)APIs have been deprecated. That means that while you can still call the APIs,if your app does not specifically target Windows 8.1, you will getWindows 8 versioning (6.2.0.0).”

但是此时你去查看应用软件PEB的信息,发现PEB里面的系统版本还是正确的,在Windows10下面调试了一下,发现但是GetVersionExW确实返回的是6.2,但是PEB里面的版本则是6.4。也就是说微软更改了这个API的实现。

去调试微软对这个API做了什么改动意义不大,反正现在的结果就是这个API返回的值不对了,API也开始说谎了~不过在[1]里面,微软同时给出一个解决方案,嗯,一边跟你说,这个API已经被废弃了,一边又说还是可以用的,这不是坑爹是什么……解决方案是什么呢?修改manifest文件。加一段compatibility节点。

<?xml version="1.0"encoding="UTF-8" standalone="yes"?>

<assembly manifestVersion="1.0"xmlns="urn:schemas-microsoft-com:asm.v1"xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">

<description> my appexe </description>

<trustInfoxmlns="urn:schemas-microsoft-com:asm.v3">

<security>

<requestedPrivileges>

<requestedExecutionLevel

level="asInvoker"

uiAccess="false"

/>

</requestedPrivileges>

</security>

</trustInfo>

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">

<application>

<!-- Windows 8.1 -->

<supportedOSId="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>

<!-- Windows Vista -->

<supportedOSId="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>

<!-- Windows 7 -->

<supportedOSId="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>

<!-- Windows 8 -->

<supportedOSId="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>

</application>

</compatibility>

</assembly>

主要就是compatibility部分了,如果你已经有manifest文件了,只需要添加compatibility部分即可。对了Windows10怎么办?貌似[1]里面还没有说啊,别急,用

<!-- Windows 10 -->

<supportedOSId="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>

就好了,怎么知道的?请叫我雷锋!在Windows10下面测试一把的结果如图。

兼容模式的影响

还有一个可能的情况会造成GetVersionExW返回的系统版本和实际的系统版本不一样。这个与Windows8.1,Windows10没有什么关系。纯粹是为了兼容考虑,在设置兼容模式之后,GetVersionExW返回的是兼容的目标版本的系统版本。启动调试去查看应用程序的PEB是不是被修改过了,结果发现,并没有修改过PEB。那么问题来了,为什么GetVersionExW的值发生变化了呢?

直接调试GetVersionExW发现,在设置兼容模式之后,微软使用IATHook的方式,Hook了一堆的(嗯,不是1-2个,而是一堆)系统API,其中GetVersionExW就被AcLayers.dll里面的一个函数给Hook了,然后Hook函数里面返回了兼容系统版本号。

怎样判断兼容模式

一般来说,应用程序不需要判断当前是否处于兼容模式下运行,微软实现这个机制的目的本意就是想对应用程序透明。主要是很多“古老的”程序内部严格限定只能在某个具体的系统下运行,譬如限定在WindowsXP SP3下运行(因为当时微软的系统最高版本可能就是XP),这样当用户操作系统升级之后,譬如升级到了Windows7,这个时候问题来了!本来一般情况下微软的系统是可以前向兼容的,结果应用程序自己主动不兼容,发现不是XP,主动退出,导致用户用不了了,因此微软发明了一个兼容模式,高版本的系统可以模拟一个低版本的系统运行环境,这样就解决大量的类似问题。

在兼容模式下,当应用程序调用GetVersionExW等API时,返回的是兼容的目标系统的系统版本,当然这只是兼容模式技术解决的一个问题而已,但是是较重要的一个问题(兼容模式还解决了很多其它问题)。

一般的应用程序不需要关心这个兼容模式。但是某些特殊的应用程序却恰恰需要,应用程序可能会根据不同的系统版本做不同的事情,而一个可能性是用户误把应用程序设置为某个低版本操作系统兼容运行,导致整个程序运行反而异常。

举个例子,像系统补丁修复程序,一般来说漏洞补丁都是和系统版本一一对应,如果程序使用GetVersionExW来获取系统版本,那么程序运行在Windows7下面,因为兼容模式的影响,导致补丁修复程序推送了一大批WindowsXP下面的补丁,想想这个场景,也是有点尴尬的。

从大部分的使用场景上面来说,放弃使用GetVersionExW也许是一个更好的选择。通过其它方式拿到更精确的系统版本,不用考虑兼容模式的副作用,也不用担心Windows8(主要是指Window8.1和Window10)以上的系统获取到错误的系统版本。

那么怎么判断当成程序正在兼容模式运行呢?方法应该有很多,比较简单的方法,[4]里面介绍过一种,不过这种方法要注意,在Windows8.1之后,它可能给出错误的结果,要按照上面提到的办法,让GetVersionExW返回正确的值。

另外一种更好的方法是判断注册表里面的应用程序兼容模式记录列表,当把一个应用程序设置为兼容模式或者管理员权限启动之后,系统会在HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers下面记录相应的信息,如果想所有用户起效,则修改HKEY_LOCAL_MACHINE\\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers即可。我们可以试试设置之后的效果。我在Windows7SP1下面随意设置了几个。

可以很清楚的看到两个程序被设置为兼容WIN7RTM运行和兼容WINXPSP3运行,如果你去掉这两个注册表值,则应用程序就不再以兼容模式运行,因此实际上可以检测这个位置判断哪些程序被设置为兼容模式运行,甚至可以通过删除这里的内容,去掉某些应用程序的兼容模式设置。同时可以发现的是微软用很容易识别的字符串来描述兼容的目标系统,更多的兼容描述字符串可以参考[3],另外要注意的是从Windows8开始,这些字符串前面多了一个波浪线和空格(~ ),譬如兼容WINXPSP3,在Windows10下面是~ WINXPSP3。

判断系统版本更好的办法

GetVersionExW既然被微软废弃了,再使用总觉得拔凉拔凉的,有什么更好的判断系统版本的方法吗?答案是肯定的!下面给出几种实践中用过的方法。

1、首先从原理上来说,GetVersionExW是读取的PEB里面的版本信息,其实我们自己也可以读取PEB嘛,只是麻烦一点。这个就不给例子了。有兴趣可以自己实现一下。

2、微软在[1]里面其实推荐过一批更好的API([7]),号称接口名更人性化,从名字上面看确实含义更清晰了,不过使用起来是否方便就仁者见仁智者见智了,随意罗列几个,不过这套API声明在<VersionHelpers.h>里面,比较新的SDK才有。

VERSIONHELPERAPI IsWindows7OrGreater()

VERSIONHELPERAPIIsWindows7SP1OrGreater()

VERSIONHELPERAPI IsWindows8OrGreater()

VERSIONHELPERAPI IsWindows8_1OrGreater()

VERSIONHELPERAPI IsWindowsServer()

3、使用VerifyVersionInfo来进行版本判断(参考[8]),这个API声明在winbase.h里面,从Windows2000系统就已经开始提供了,但是我们可能很少使用,说实话,使用起来不是特别方便。我们先看看是怎么使用的,它本质是进行版本比较。

BOOL WINAPI VerifyVersionInfo(

_In_  LPOSVERSIONINFOEX lpVersionInfo,

_In_  DWORD dwTypeMask,

_In_  DWORDLONG dwlConditionMask

);

这个函数的原型里面第一个参数是熟悉的OSVERSIONINFOEX,但是这里是做为传入参数使用,第二个参数dwTypeMask用于指定要比较哪些项,可以比较主版本,次版本,Build号等等,可以使用位组合。第三个参数则是比较的方法,是>、=还是<,或者>=,<=等等,可以通过VER_SET_CONDITION来设置,可以进行各种组合来判断,还是比较灵活的。看两个例子吧。

BOOL IsWinVerGreaterThan(DWORDdwMajorVersion, DWORD dwMinorVersion)

{

OSVERSIONINFOEXW osvi = {0};

DWORDLONG dwlConditionMask = 0;

ZeroMemory(&osvi, sizeof(osvi));

osvi.dwOSVersionInfoSize= sizeof(osvi);

osvi.dwMajorVersion= dwMajorVersion;

osvi.dwMinorVersion= dwMinorVersion;

// 主版本号判断

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER);

if (::VerifyVersionInfoW(&osvi, VER_MAJORVERSION, dwlConditionMask))

return TRUE;

// 次版本号判断

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);

VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER);

return ::VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);

}

//-------------------------------------------------------------------------

// 函数    : IsWinVerEqualTo

// 功能    : 判断是否=某个特定的系统版本

// 返回值  : BOOL

// 参数    : DWORD dwMajorVersion

// 参数    : DWORD dwMinorVersion

// 附注    :

//-------------------------------------------------------------------------

BOOL IsWinVerEqualTo(DWORDdwMajorVersion, DWORD dwMinorVersion)

{

OSVERSIONINFOEXW osvi = {0};

DWORDLONG dwlConditionMask = 0;

// 1、初始化系统版本信息数据结构

ZeroMemory(&osvi, sizeof(osvi));

osvi.dwOSVersionInfoSize= sizeof(osvi);

osvi.dwMajorVersion= dwMajorVersion;

osvi.dwMinorVersion= dwMinorVersion;

// 2、初始化条件掩码

VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);

VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);

return ::VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);

}

封装一下使用就更方便了,譬如要判断当前是Window7,用IsWinVerEqualTo(6,1)即可。或者你不想暴露一些“恶心”的MagicNumber,可以再封装一个IsWindows7()嘛。

4、还有一个我个人比较喜欢的方法是使用一个未文档化的函数来获取系统版本,也就是RtlGetNtVersionNumbers,这个是NTDLL里面的一个未文档化函数。但是这个函数微软把它导出了,因此我们就有办法使用了。

使用方法:

//-------------------------------------------------------------------------

// 函数    : GetNtVersionNumbers

// 功能    : 调用RtlGetNtVersionNumbers获取系统版本信息

// 返回值  : BOOL

// 参数    : DWORD& dwMajorVer 主版本

// 参数    : DWORD& dwMinorVer 次版本

// 参数    : DWORD& dwBuildNumber build号

// 附注    :

//-------------------------------------------------------------------------

BOOL GetNtVersionNumbers(DWORD&dwMajorVer, DWORD& dwMinorVer,DWORD& dwBuildNumber)

{

BOOL bRet= FALSE;

HMODULE hModNtdll= NULL;

if (hModNtdll= ::LoadLibraryW(L"ntdll.dll"))

{

typedef void (WINAPI *pfRTLGETNTVERSIONNUMBERS)(DWORD*,DWORD*, DWORD*);

pfRTLGETNTVERSIONNUMBERS pfRtlGetNtVersionNumbers;

pfRtlGetNtVersionNumbers = (pfRTLGETNTVERSIONNUMBERS)::GetProcAddress(hModNtdll, "RtlGetNtVersionNumbers");

if (pfRtlGetNtVersionNumbers)

{

pfRtlGetNtVersionNumbers(&dwMajorVer, &dwMinorVer,&dwBuildNumber);

dwBuildNumber&= 0x0ffff;

bRet = TRUE;

}

::FreeLibrary(hModNtdll);

hModNtdll = NULL;

}

return bRet;

}

使用未文档化的函数要注意的一个点是,需要分析清楚函数的传入参数的类型,否则传错了类型,如果类型大小不一样,轻则函数出错,重则程序崩溃(尤其是传出参数)。我们可以看下RtlGetNtVersionNumbers这个函数是怎么实现的(调试用的ntdll.dll的版本是6.1.7601.18247,其它系统的也差不多的,仅仅是Hardcode的数字不一样),下面是它的实现伪码(IDA生成)。

int __stdcall RtlGetNtVersionNumbers(int a1, int a2, int a3)

{

int result; // eax@5

if ( a1 )

*(_DWORD *)a1 = 6;

if ( a2 )

*(_DWORD *)a2 = 1;

result = a3;

if ( a3 )

*(_DWORD *)a3 = 0xF0001DB1u;

return result;

}

我只能说微软,你干得漂亮!直接Hardcode处理,简单干净!

5、还有一种方法是直接去获取NTDLL这个系统关键文件(其它的文件也可行,但是实践证明NTDLL最好)的文件版本号,一般来说,该文件的版本基本上就是系统的版本。像[4]里面用到的判断兼容的方法就是通过对比GetVersionEx的返回值和关键系统文件的版本,来判断是否当前应用程序处理兼容模式下

注:建议不要使用RtlGetVersion来进行版本判断。Windows2003之前它的行为在兼容模式下和GetVersionExW不一致,Vista之后在兼容模式下它的行为和GetVersionExW一致。

效果展示

分别在WindowsXP,Windows7,Windows10下面测试了这些方法。注意左边的是常规模式运行,右边的是兼容模式运行。

参考文献

[1] Operating system version changes inWindows 8.1 and Windows Server 2012 R2 http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074(v=vs.85).aspx

[2] GetVersionExhttp://msdn.microsoft.com/en-us/library/ms724451(VS.85).aspx

[3] Running an Application asAdministrator or in Compatibility Mode http://www.verboon.info/2011/03/running-an-application-as-administrator-or-in-compatibility-mode/

[4] http://blog.csdn.net/magictong/article/details/5829065怎样判定应用程序自身运行在“兼容模式”下?

[5] http://blogs.msdn.com/b/chuckw/archive/2013/09/10/manifest-madness.aspxManifestMadness

[6] OSVERSIONINFOEX structure http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx

[7] Version Helper functions http://msdn.microsoft.com/en-us/library/windows/desktop/dn424972(v=vs.85).aspx

[8] VerifyVersionInfofunction http://msdn.microsoft.com/en-us/ms725492(VS.85).aspx

Windows系统版本判定那些事儿的更多相关文章

  1. Windows系统版本判定那些事儿[转]

    Windows系统版本判定那些事儿 转自CSDN,原文链接,我比较不要脸, 全部给复制过来了 前言 本文并不是讨论Windows操作系统的版本来历和特点,也不是讨论为什么没有Win9,而是从程序员角度 ...

  2. Windows系统版本判定那些事儿(有图,各种情况,很清楚)

    前言 本文并不是讨论Windows操作系统的版本来历和特点,也不是讨论为什么没有Win9,而是从程序员角度讨论下Windows获取系统版本的方法和遇到的一些问题.在Win8和Win10出来之后,在获取 ...

  3. Windows系统版本号判定那些事儿

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  4. Windows 系统版本判断

    Windows 系统版本判断 博客分类: C/C++   很多情况下,需要软件判断操作系统,其实网上写的都很少,我希望这篇文章能给大家帮助 首先我们要在.h中定义下面的东西 #define PRODU ...

  5. inno setup判断是Windows系统版本(其实还是Delphi代码,还能检查域控制器和家庭版)

    1.设置Windows最低版本要求 [Setup]: MinVersion 格式: a.bb,c.dd,这里 a.bb 是 Windows 版本,c.dd 是 Windows NT 版本. 默认值: ...

  6. ios 宏定义 系统版本 判定

    当需要判断iOS系统版本的时候,相信很多人都会这么干: #define SystemVersion [[UIDevice currentDevice] systemVersion].floatValu ...

  7. Delphi 10.3版本获取windows系统版本和CPU信息

    procedure TForm1.Button1Click(Sender: TObject); var mm:TRegistry; cpu:string; begin mm:=TRegistry.Cr ...

  8. Windows系统版本型号MSDN版、OEM版、RTM版、VOL版区别

    我们常常听说操作系统的MSDN版.OEM版.RTM版.VOL版等等,它们到底是什么意思,有什么不同呢? (一)MSDN (Microsoft Developer Network)版MSDN软件是微软公 ...

  9. 使用bat脚本永久激活Windows系统

    每次重装完系统后,右下角会提示系统未激活,无法进行一些个性化设置. 在这里我自己写了一个bat脚本用于激活Windows系统.(仅供学习) 文件下载: 链接:https://pan.baidu.com ...

随机推荐

  1. Leetcode解题-链表(2.2.1)AddTwoNumbers

    1 题目:2.2.1 Add Two Numbers You are given two linked lists representing two non-negative numbers. The ...

  2. 开源框架Volley的使用《二》[NetWorkImageView&&LruCache&ImageLoader]

    转载本专栏每一篇博客请注明转载出处地址,尊重原创.此博客转载链接地址:小杨的博客    http://blog.csdn.net/qq_32059827/article/details/5278849 ...

  3. 在电脑上安装Linux操作系统

    1硬件需求 A 一台电脑 B 一个优盘 2软件需求 A制作优盘启动盘的软件PowerISO BLinux操作系统的镜像文件 3安装PowerISO,并使用PowerISO A安装PowerISO B插 ...

  4. Android 在 SElinux下 如何获得对一个内核节点的访问权限

    点击打开链接 Android 5.0下,因为采取了SEAndroid/SElinux的安全机制,即使拥有root权限,或者对某内核节点设置为777的权限,仍然无法在JNI层访问. 本文将以用户自定义的 ...

  5. 【移动开发】自定义ProgressBar

    <ProgressBar android:layout_centerInParent="true" android:layout_width="30dp" ...

  6. android开源库发布到jcenter图文详解与填坑

    相信很多人都用过开源项目,特别是android studio普及以后,使用开源库更方便简单.而如何上传开源库到jcenter供大家方便使用,虽然网上也有教程,但还是遇坑了,最后总结一下,希望可以帮助大 ...

  7. erMaster插件

    需求: 在做开源项目时,了解基本业务后.试图从数据库表设计来分析项目.通过visio时绘制操作繁琐,另外不能与数据库连动.于是想找一款快速绘制er图,并且能够和数据库连动的软件工具. eclipse插 ...

  8. 使用JavaScript在Canvas上画出一片星空

    随着Html5的迅猛发展,画布也变得越来越重要.下面我就写一个关于在canvas上画出一片星空的简单的代码的示例. 理论基础 初始化一个canvas,获得一个用于绘制图形的上下文环境context.并 ...

  9. Mysql insert语句的优化

    1) 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句.这比使用分开INSERT语句快(在一些情况中几倍).    Insert into test values(1,2),(1,3), ...

  10. (NO.00003)iOS游戏简单的机器人投射游戏成形记(十七)

    现在玩家选择机器人后,可以在屏幕上或手臂上点击来移动robot's arm了. 但是玩家选择一个机器人后没有视觉效果来表明哪个机器人被选中.玩家做了一个操作后没有视觉反馈会惹恼强迫症用户滴 ;) 这篇 ...