当我们的应用程序或者驱动程序需要根据操作系统的版本来选择执行不同的代码的时候,我们应该知道这部分的内容。
这篇文章花费了我好一段时间,在附件里有三份代码分别代表了三种不同的方法。这些内容都是前人的成果,鄙人纯属老生常谈。
废话少说,我们进入主题。
用户模式下的方法:
方法 1 :
利用 Win32 API 可以很容易的确定所安装的操作系统的版本。我们可以使用 GetVersionEx 函数,在 MSDN 中可以很容易的找到关于它的详细解释,什么那位同学看不懂 E 文,那我只好解释一下啦。
GetVersionEx 函数的原型为:
BOOL GetVersionEx(LPOSVERSIONINFO lpVersionInformation );
我们需要传递一个叫做 OSVERSIONINFO 的结构的指针,函数返回一个 BOOL 值,非零表示成功;零表示失败的,这时可以使用 GetLastError 得到错误码,使用 Visual stdio 附带的 ERROR LOOK.EXE 可以很轻易的得到详细的错误提示。
需要注意的一点就是在使用之前,我们必须填充 OSVERSIONINFO 的 dwOSVersionInfoSize 字段,操作系统根据这个字段判断我们使用的是 OSVERSIONINFO 结构还是 OSVERSIONINFO EX 结构。
哈哈,忘了说这个结构的具体内容了。
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize; // 设置为 OSVERSIONINFO 结构的大小
DWORD dwMajorVersion; // 操作系统的主版本号
DWORD dwMinorVersion; // 操作系统的副版本号
DWORD dwBuildNumber; // 操作系统 Build (构建)编号
DWORD dwPlatformId; // 操作系统的平台
TCHAR szCSDVersion[128]; // 服务补丁号
} OSVERSIONINFO;
在这里我们只使用 OSVERSIONINFO 结构,因为 GetVersionEx 使用的是 OSVERSIONINFO 结构类型的指针,如果使用 OSVERSIONINFOEX 的话,会涉及到结构指针类型的转换问题,对于这个问题我没有很好的解决方案,如果你有的话请发扬一下共享精神,告诉我一下哈哈。
下面的这个表节选自 http://www.codeproject.com/ ,我对这个表格添加了一些内容。通过这个表可以很清楚的查到大部分操作系统对应字段的值。
| dwPlatformID | dwMajorVersion | dwMinorVersion | dwBuildNumber |
95 | 1 | 4 | 0 | 950 |
95 SP1 | 1 | 4 | 0 | > 950 && <= 1080 |
95 OSR2 | 1 | 4 | < 10 | > 1080 |
98 | 1 | 4 | 10 | 1998 |
98 SP1 | 1 | 4 | 10 | >1998 && < 2183 |
98 SE | 1 | 4 | 10 | >= 2183 |
Me | 1 | 4 | 90 | 3000 |
| ||||
NT 3.51 | 2 | 3 | 51 | 1057 |
NT 4 | 2 | 4 | 0 | 1381 |
2000 | 2 | 5 | 0 | 2195 |
XP | 2 | 5 | 1 |
|
| ||||
CE 1.0 | 3 | 1 | 0 |
|
CE 2.0 | 3 | 2 | 0 |
|
CE 2.1 | 3 | 2 | 1 |
|
CE 3.0 | 3 | 3 | 0 |
|
| ||||
Vista |
| 6 | 0 |
|
我所写的代码是这样子的:
.data
osVersion OSVERSIONINFO <?>
.code
; 先填充 dwOSVersionInfoSize 为 OSVERSIONINFO 结构的大小
mov osVersion.dwOSVersionInfoSize, SIZEOF OSVERSIONINFO
invoke GetVersionEx, addr osVersion
.if eax == 0
invoke GetLastError ; 得到错误号
.else
.if osVersion.dwMajorVersion == 4 ;NT4.0
.elseif osVersion.dwMajorVersion == 5
.if osVersion.dwMinorVersion == 0 ;Win 2000
.elseif osVersion.dwMinorVersion == 1 ;XP
.elseif osVersion.dwMinorVersion == 2 ;Win 2003
.endif
.elseif osVersion.dwMajorVersion == 6 &&/
osVersion.dwMinorVersion==0 ;Windows Vista
.endif
.endif
当然这只是关键代码,详细代码见附件 os OSVERSIONINFO 目录,其中 os.asm 为源文件, os.rc 为资源文件, os.exe 为生成的可执行文件。按照常规方式链接,请注意查看各个包含文件的所在目录的层次是不是和你的机器是一样的。
方法 2 :
查询注册表 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/ 子键下的值,其中 CSDVersion 键包含关于服务补丁包的字符串; CurrentBuildNumber 包含操作系统的构建编号; CurrentVersion 一小数形式包含内核的主要版本和次要版本等等信息等等。如果有足够权限的话,可以在运行中输入 ”regedt32” 或 ”regedit” 查看相应的键值。
我们使用 RegOpenKeyEx 打开注册表 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/ 子键,然后使用 RegQueryValueEx 查询相应的键值,完成任务后使用 RegCloseKey 关闭子键句柄。
以下内容节选自罗云彬老师的教材:
1. 打开和关闭子键
注册表函数对注册表的操作是通过句柄来完成的,与文件操作一样,在对某个键下的子键或者键值项进行操作之前,需要先将这个键打开,然后使用键句柄来引用这个键,在操作完毕以后再将键句柄关闭。注册表的根键不需要打开,它们的句柄是固定不变的,要使用根键的时候只要把这些句柄直接拿来用就是了, Windows.inc 中已经预定义了它们的数值:
HKEY_CLASSES_ROOT equ 80000000h
HKEY_CURRENT_USER equ 80000001h
HKEY_LOCAL_MACHINE equ 80000002h
HKEY_USERS equ 80000003h
HKEY_PERFORMANCE_DATA equ 80000004h
HKEY_CURRENT_CONFIG equ 80000005h
HKEY_DYN_DATA equ 80000006h
在程序中可以随时将这些助记符当做句柄来引用对应的根键。在程序结束的时候,不需要关闭这些根键句柄。
打开子键使用 RegOpenKeyEx 函数,在 Win16 中还存在一个 RegOpenKey 函数,虽然在 Win32 中这个函数仍然存在,但这仅是为了兼容的目的而设置的。 API 手册中推荐使用 RegOpenKeyEx 函数:
invoke RegOpenKeyEx,hKey,lpSubKey,dwOptions,samDesired,phkResult
函数的 hKey 参数指定父键句柄, lpSubKey 指向一个字符串,用来表示要打开的子键名称,在系统中一个子键的全称是以“根键 / 第 1 层子键 / 第 2 层子键 / 第 n 层子键”类型的字符串表示的,中间用“ / ”隔开,字符串的最后以 0 字符结束,这和目录名的表示方法是很像的。
既然子键的全称是这样表示的,那么要打开一个子键的时候,下面的两种表示方法有什么不同呢?
( 1 )父键= HKEY_LOCAL_MACHINE ,子键= Software/RegTest/MySubkey
( 2 )父键= HKEY_LOCAL_MACHINE/Software ,子键= RegTest/MySubkey
答案是:这两种表示方法是完全相同的。在使用 RegOpenKeyEx 函数打开子键的时候,既可以将 hKey 参数设置为 HKEY_LOCAL_MACHINE 根键的句柄,并将 lpSubKey 参数指向“ Software/RegTest/MySubkey ”字符串;也可以将 hKey 参数设置为“ HKEY_LOCAL_ MACHINE/Software ”的句柄,将 lpSubKey 参数指向“ RegTest/MySubkey ”字符串,得到的结果是一样的。但是,使用第一种方法时, hKey 参数可以直接使用助记符 HKEY_LOCAL_ MACHINE 来表示,因为根键的句柄是固定的,不需要打开;而使用第二种方法时,还需要先打开“ HKEY_LOCAL_MACHINE/Software ”键来获取它的句柄,所以具体使用哪种方法还要根据具体情况灵活选用。
函数的其他几个参数的含义如下。
● dwOptions 参数——系统保留参数,必须指定为 0 。
● samDesired 参数——子键的打开方式,根据使用子键的方式,可以设置为下列取值的组合,只有指定了打开的方式,才能在打开子键后进行相应的操作:
■ KEY_ALL_ACCESS ——允许所有的存取。
■ KEY_CREATE_LINK ——允许建立符号列表。
■ KEY_CREATE_SUB_KEY ——允许建立下一层子键。
■ KEY_ENUMERATE_SUB_KEYS ——允许枚举下一层子键。
■ KEY_EXECUTE ——允许读操作。
■ KEY_QUERY_VALUE ——允许查询键值数据。
■ KEY_READ — KEY_QUERY_VALUE , KEY_ENUMERATE_SUB_KEYS 和 KEY_ NOTIFY 的组合。
■ KEY_SET_VALUE ——允许修改或创建键值数据。
■ KEY_WRITE —— KEY_SET_VALUE 和 KEY_CREATE_SUB_KEY 的组合。
● phkResult 参数——指向一个双字变量,函数在这里返回打开的子键句柄。
如果函数执行成功,返回值是 ERROR_SUCCESS ,并且函数在 phkResult 参数指向的变量中返回子键句柄。
当不再需要继续使用键句柄的时候,可以使用 RegCloseKey 函数将它关闭:
invoke RegCloseKey,hKey
如果句柄被成功关闭,函数返回 ERROR_SUCCESS 。
2. 查询键值数据
读取键值项中的数据或者查询键值项的属性使用 RegQueryValueEx 函数,用法如下:
invoke RegQueryValueEx,hKey,lpValueName,lpReserved,/
lpType,lpData,lpcbData
参数 hKey 和 lpValueName 用来指定要读取的键值项所处的子键句柄和键值项的名称, lpReserved 参数是保留参数,必须使用 0 。 lpData 参数指向一个缓冲区,用来接收返回的键值数据。
函数的其余几个参数使用时必须注意的是它们都是指向双字变量的指针,这一点和使用 RegSetValueEx 函数时是不同的:
● lpType 参数——函数在这个参数指向的双字变量中返回读取的键值类型,如果不需要返回键值项的类型,可以将这个参数设置为 NULL 。
● lpcbData 参数——在调用的时候,程序必须在这个参数指向的双字变量中放置缓冲区的长度(并不是直接用 lpcbData 参数指出缓冲区长度)。当函数返回的时候,双字变量被函数改为返回到缓冲区中的数据的实际长度。
当函数执行成功的时候,函数的返回值是 ERROR_SUCCESS 。当程序指定的缓冲区长度不足以容纳返回的数据的时候,函数的返回值是 ERROR_MORE_DATA ,这时 lpcbData 参数指向的双字变量中返回需要的长度。
如果仅需要查询键值长度而不需要返回实际的数据,可以将 lpData 参数设置为 NULL ,但是 lpcbData 参数不能为 NULL ,这时函数会在 lpcbData 参数指向的双字变量中返回键值数据的长度。如果仅想查询键值项的类型,也可以同时将 lpcbData 和 lpData 参数设置为 NULL 。在这些情况下如果函数查询成功,返回值也是 ERROR_SUCCESS 。
如果要在一个键中查询键值数据的话,键的打开方式中必须包括 KEY_QUERY_VALUE 方式。
详细代码见 os Reg 目录下的源代码文件。
内核模式下的方法:
方法 1 :
使用 ZwQueryKey , ZwQueryKey , ZwClose 等函数查询注册表,获取相应的键值。在 Kmdkit 驱动开发包的 Kdmkit/examples/basic/RegistryWorks 目录下有关于内核模式操作注册表的示例。
方法 2 :
使用 PsGetVersion 函数。它的原型是
BOOLEAN PsGetVersion (
PULONG MajorVersion OPTIONNAL ,
PULONG MinorVersion OPTIONNAL ,
PULONG BuildNumber OPTIONNAL ,
PUNICODE_STRING CSDVersion OPTIONNAL ,
);
在 Win XP 和 Win 2003 中还支持 RtlGetVersion API ,它也使用之前的 OSVERSIONINFO 结构的指针作为参数。
函数原型为:
NTSTATUS RtlGetVersion ( PRTL_OSVERSIONINFOW lpVersionInfomation );
在这里我们只使用 PsGetVersion 。
.data
dwMajorVersion dd 0
dwMinVersion dd 0
dwBuildNumber dd 0
usz dw 256 dup(0)
usCSDVersion UNICODE_STRING {sizeof usz-2, sizeof usz,offset usz}
.code
invoke PsGetVersion,addr dwMajorVersion,addr dwMinVersion,/
addr dwBuildNumber,addr usCSDVersion
.if dwMajorVersion == 4 ;NT4.0
.elseif dwMajorVersion == 5
.if dwMinVersion == 0 ;Win 2000
.elseif dwMinVersion == 1 ;Win XP
.elseif dwMinVersion == 2 ;Win 2003
.endif
.elseif dwMajorVersion == 6 && dwMinVersion == 0 ;Vista
.endif
接下来可以处理 usCSDVersion UNICODE_STRING 得到服务补丁号。
目前常见的方法是这些,“如果你有什么好方法的话,请给我们来信吧,与大家一同分享”, ^_^ 。
转自:http://www.aogosoft.com/downpage.asp?mode=viewtext&id=204