Windows8.1でもバージョン情報を正しく取得するには?

というわけでプログラムネタ継続です。

元々は「Windows8.1(Beta,RTM)でGetVersionExを使って取得しても正しいバージョンが取得できない(ことがある)」というところから端を発します。

どうもWindows8.1のタイミングでOS情報の取得に対してエミュレーションをかけるようになったようです。

プログラムの互換モードをさらに強くしたような感じです。

プログラムのマニフェストに対象のOSをサポートしていると記述しないとエミュレーションされてしまう

詳しくはOperating system versioning – MSDNで。

マニフェストのOS互換情報にWindows8.1でも動作させることができるよ~と記述しないとマニフェストに検知できる中で最大のOSのバージョンとして処理されます。

なお、WindowsVistaとWindows7の時にもDirectXの動きやらで互換問題がありましたが、Windows7とWindows8にもありますので記述するなら要注意です。

バージョンを確認するならGetVersionExではなく「VersionHelpers.h」にあるテストルーチンを使え、とあるが・・・

ただこのルーチン、VisualStudio2013もしくはWindows8.1 SDKを持ってこないと使えないらしいのです。

いろいろ調べると中身はVerifyVersionInfoを使ってバージョン情報を照合しているだけ「らしい」ので自前で組んでみました。

使えるかどうかはWindows8.1を持っていないため不明ですがたぶん大丈夫のような気がします。

コード自体は2時間程度で簡易デバッグまで持って行ったものなのでちょっと不安だったり。

というわけでソースコード。まずはヘッダから

#ifndef __vercheck_h__
#define __vercheck_h__
//Windowsのバージョンを調べる
BOOL WINAPI IsGreaterWindowsVersion(int nMajor,int nMinor,int nServicePack,int nProductType);
//Windowsのバージョンをできる限り詳しく調べる
BOOL WINAPI GetWindowsVersionDetails(OSVERSIONINFOEX *lpOSVersion);
//VersionHelperAPIをこちらで実装してみたもの
#define VERSIONHELPERAPI FORCEINLINE BOOL
VERSIONHELPERAPI IsWindowsXPOrGreater(void) { return IsGreaterWindowsVersion(5,1,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsXPSP1OrGreater(void) { return IsGreaterWindowsVersion(5,1,1,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsXPSP2OrGreater(void) { return IsGreaterWindowsVersion(5,1,2,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsXPSP3OrGreater(void) { return IsGreaterWindowsVersion(5,1,3,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsVistaOrGreater(void) { return IsGreaterWindowsVersion(6,0,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsVistaSP1OrGreater(void) { return IsGreaterWindowsVersion(6,0,1,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsVistaSP2OrGreater(void) { return IsGreaterWindowsVersion(6,0,2,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindows7OrGreater(void) { return IsGreaterWindowsVersion(6,1,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindows7SP1OrGreater(void) { return IsGreaterWindowsVersion(6,1,1,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindows8OrGreater(void) { return IsGreaterWindowsVersion(6,2,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindows8Point1OrGreater(void) { return IsGreaterWindowsVersion(6,3,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindows8_1OrGreater(void) { return IsGreaterWindowsVersion(6,3,0,VER_NT_WORKSTATION); }
VERSIONHELPERAPI IsWindowsServer(void) { return IsGreaterWindowsVersion(-1,-1,-1,VER_NT_SERVER); }
#endif //__vercheck_h__

VersionHelpers.hにあるはずのIsWindows~を自前のIsGreaterWindowsVersionに置き換えています。

あと、GetVersionExでは正しいバージョンの取得ができないと言うことでVerifyVersionInfoを使って取得する関数も一緒に作りました。

ちなみに宣言にWINAPIを使っているのは気分です。

本体コード

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stddef.h>
#include "vercheck.h"
//OSVERSIONINFOの値を検索するときの要素
typedef struct _OSVersionElement{
	size_t offset;
	size_t varsize;
	DWORD typemask;
	DWORD minval,maxval;
} OSVERSIONELEMENT;
//指定したポインタのオフセットに値を代入する
template <class VarType> void StoreValueToPointerTemplate(void *ptr,size_t offset,VarType value)
{
	*(VarType *)((unsigned char *)ptr + offset) = value;
}
static void StoreValueToPointer(void *ptr,size_t offset,size_t varsize,unsigned int value)
{
	switch(varsize){
	case 1: StoreValueToPointerTemplate(ptr,offset,(unsigned char)value); break;
	case 2: StoreValueToPointerTemplate(ptr,offset,(unsigned short)value); break;
	case 4: StoreValueToPointerTemplate(ptr,offset,(unsigned int)value); break;
	//case 8: StoreValueToPointerTemplate(ptr,offset,(unsigned long long)value); break;
	//default: __assume(0);
	}
}
//OSVERSIONINFOの値を取得する
static BOOL GetOSVersionValue(OSVERSIONINFOEX *verinfo,const OSVERSIONELEMENT &verelem)
{
	if(!(verelem.varsize == 1 || verelem.varsize == 2 || verelem.varsize == 4)) return FALSE;
	if(verelem.minval < verelem.maxval){
		DWORDLONG conditiongreater,conditionequal; DWORD nMin,nMax,nMid;
		conditionequal = conditiongreater = 0;
		VER_SET_CONDITION(conditiongreater,verelem.typemask,VER_GREATER);
		VER_SET_CONDITION(conditionequal,verelem.typemask,VER_EQUAL);
		nMin = verelem.minval; nMax = verelem.maxval;
		while(nMin != nMax){
			nMid = (nMax + nMin) / 2;
			StoreValueToPointer(verinfo,verelem.offset,verelem.varsize,nMid);
			if(VerifyVersionInfo(verinfo,verelem.typemask,conditionequal)){ nMin = nMax = nMid; }
			else if(VerifyVersionInfo(verinfo,verelem.typemask,conditiongreater)){ nMin = nMid + 1; }
			else{ nMax = nMid; }
		}
	}
	else{
		DWORDLONG conditionand; DWORD mask,value; unsigned int bits;
		conditionand = 0; mask = 0x00000000; bits = (unsigned int)(verelem.varsize * 8);
		VER_SET_CONDITION(conditionand,verelem.typemask,VER_AND);
		
		for(unsigned int i = 0;i < bits;i++){
			value = 1 << i;
			StoreValueToPointer(verinfo,verelem.offset,verelem.varsize,value);
			if(VerifyVersionInfo(verinfo,verelem.typemask,conditionand)){ mask |= value; }
		}
		StoreValueToPointer(verinfo,verelem.offset,verelem.varsize,mask);
	}
	return TRUE;
}
//Windowsのバージョンを調べる
BOOL WINAPI IsGreaterWindowsVersion(int nMajor,int nMinor,int nServicePack,int nProductType)
{
	OSVERSIONINFOEX verinfo; DWORD dwTypeMask; DWORDLONG dwlConditionMask;
	dwTypeMask = 0x00000000; dwlConditionMask = 0x0000000000000000ull;
	memset(&verinfo,0x00,sizeof(verinfo));
	verinfo.dwOSVersionInfoSize = sizeof(verinfo);
	if(nMajor >= 0){ verinfo.dwMajorVersion = nMajor; dwTypeMask |= VER_MAJORVERSION; VER_SET_CONDITION(dwlConditionMask,VER_MAJORVERSION,VER_GREATER_EQUAL); }
	if(nMinor >= 0){ verinfo.dwMinorVersion = nMinor; dwTypeMask |= VER_MINORVERSION; VER_SET_CONDITION(dwlConditionMask,VER_MINORVERSION,VER_GREATER_EQUAL); }
	if(nServicePack >= 0){ verinfo.wServicePackMajor = (WORD)nServicePack; dwTypeMask |= VER_SERVICEPACKMAJOR; VER_SET_CONDITION(dwlConditionMask,VER_SERVICEPACKMAJOR,VER_GREATER_EQUAL); }
	if(nProductType >= 0){ verinfo.wProductType = (BYTE)nProductType; dwTypeMask |= VER_PRODUCT_TYPE; VER_SET_CONDITION(dwlConditionMask,VER_PRODUCT_TYPE,VER_EQUAL); }
	return VerifyVersionInfo(&verinfo,dwTypeMask,dwlConditionMask);
}
#define OSVERSION_ELEMINFO(oselem,info,elem) (oselem).offset = offsetof(OSVERSIONINFOEX,elem); (oselem).varsize = sizeof(info . elem);
//Windowsのバージョンをできる限り詳しく調べる
BOOL WINAPI GetWindowsVersionDetails(OSVERSIONINFOEX *lpOSVersion)
{
	OSVERSIONINFOEX info; OSVERSIONELEMENT element[8]; size_t i,copylen;
	
	if(lpOSVersion == NULL) return FALSE;
	memset(&info,0x00,sizeof(info));
	info.dwOSVersionInfoSize = sizeof(info);
	OSVERSION_ELEMINFO(element[0],info,dwMajorVersion); element[0].typemask = VER_MAJORVERSION; element[0].minval = 0; element[0].maxval = 16;
	OSVERSION_ELEMINFO(element[1],info,dwMinorVersion); element[1].typemask = VER_MINORVERSION; element[1].minval = 0; element[1].maxval = 16;
	OSVERSION_ELEMINFO(element[2],info,dwBuildNumber); element[2].typemask = VER_BUILDNUMBER; element[2].minval = 0; element[2].maxval = 100000;
	OSVERSION_ELEMINFO(element[3],info,dwPlatformId); element[3].typemask = VER_PLATFORMID; element[3].minval = 0; element[3].maxval = 8;
	OSVERSION_ELEMINFO(element[4],info,wServicePackMajor); element[4].typemask = VER_SERVICEPACKMAJOR; element[4].minval = 0; element[4].maxval = 16;
	OSVERSION_ELEMINFO(element[5],info,wServicePackMinor); element[5].typemask = VER_SERVICEPACKMINOR; element[5].minval = 0; element[5].maxval = 16;
	OSVERSION_ELEMINFO(element[6],info,wSuiteMask); element[6].typemask = VER_SUITENAME; element[6].minval = 0; element[6].maxval = 0;
	OSVERSION_ELEMINFO(element[7],info,wProductType); element[7].typemask = VER_PRODUCT_TYPE; element[7].minval = 0; element[7].maxval = 16;
	for(i = 0;i < 8;i++){ GetOSVersionValue(&info,element&#91;i&#93;); }
	if(info.wServicePackMajor > 0){ wsprintf(info.szCSDVersion,TEXT("Service Pack %d"),info.wServicePackMajor); }
	copylen = (lpOSVersion->dwOSVersionInfoSize >= info.dwOSVersionInfoSize ? info.dwOSVersionInfoSize : lpOSVersion->dwOSVersionInfoSize) - sizeof(DWORD);
	memcpy((BYTE *)lpOSVersion + sizeof(DWORD),(BYTE *)&info + sizeof(DWORD),copylen);
	return TRUE;
}

ちなみにかなり汚いです。急ごしらえなせいでもあります。

IsGreaterWindowsVersionは素直にVerifyVersionInfoを呼び出しているだけなので難しいことはないです。

問題はGetWindowsVersionDetailsです。さすがに作るのが面倒でした。

調べる各要素に関しては力業です。一応配列と構造体でそれ「らしき」ものは作っていますが。

調べる方法は番号になっているものは二分探索、マスクになっているものはマスクビットチェックを行っています。

範囲については適当です。もしかしたらはみ出すこともあるかもしれません。また、何らかの理由で番号が調べられないときは最小を返すようにしています。

一番面倒なのは「構造体内の要素のサイズが異なっている」というところです。

offsetofと変数サイズ検知をあわせて使って何とかしのいでいますが、もっときれいな書き方があるかもしれません。

なお、使い方はほぼそのまんまです。GetVersionExの代わりにGetWindowsVersionDetailsを使えばいいだけですし、IsWindows~はその通りなら0以外を返すだけですので。

あとはADVエンジンのOS検知部分をこれで入れ替えないと・・・

ついでにマニフェストにOSサポートの記述も追加してチェックをしないと、というところですか。

まあ確かに対象のプログラムが使えないバージョンを渡しても意味がない(逆に仕様を間違えるかも)ことには同意しますが。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL