RtlGetVersion APIによりWindowsバージョン及びOS名を取得(Windows 10に対応)

icon 項目のみ表示/展開表示の切り替え

概要

Windowsのバージョンを表示します。GetVersionEX APIはマニフェストを設定しないとWindows 8.1などに対応していないのは既知となっていますが、VerfyVersionInfo APIでもWindows 10ではWindows 7相当の結果が取得されました。
ここでは、ドライバの開発などに用いるRtlGetVersion APIを使用してWindows 10でも正常に動作するプログラムを作成しました。
本来RtlGetVersion APIをVsiaul C++で使用するにはntddk.hとNtoskml.libが必要であり、このファイルはおそらくWindows DDKに含まれていると思われます。
API自身はntddl.ddlというありふれたDLLの中にあります。
本プログラムはDDKをインストールしていない環境でもコンパイルできるように、APIを動的に呼び出すようにソースコードの中で記述しています。
マクロソフトのホームページによるとAPIの引数は構造体RTL_OSVERSIONINFOWおよびRTL_OSVERSIONINFOEXW となっていますが、 実態は、windows.hからインクルードされるwinnt.hのOSVERSIONINFOWおよびOSVERSIONINFOEXWと同じです。
サービスパックの情報は文字列で返されますが、RtlGetVersion APIはユニコード版しか存在しないので、マルチバイトでも使用しやすいようにヘルパー関数GetVersion2を経由して呼び出します。
マルチバイトでコンパイルした場合は、ユニコードをANSIに変換するソースが有効になります。

テスト環境

コンパイラ

Visual C++ 2005/2008/2010/2013 Express 32/64bit マルチバイト/UNICODE

実行環境

ハイパーリンクをクリックすると実行したときの画面が表示されます。
本ソフトの実行状況、マイコンピュータのプロパティをプリントスクリーンした状態です。
マニフェストは指定していません。
メンバー 98SE 2000 XP Vista 7 8.1 2012 10 technical preview Build 9926 10 technical preview Build 10041 10 technical preview Build 10061
dwMajorVersion 4 5 5 6 6 6 6 10 10 10
dwMinorVersion 10 0 1 0 1 3 2 0 0 0
dwBuildNumber 67766446 2195 2600 6002 7601 9600 9200 9926 10041 10061
dwPlatformId 1 2 2 2 2 2 2 2 2 2
szCSDVersion A Service Pack 4 Service Pack 3 Service Pack 2 Service Pack 1
wProductType 0 1 1 1 1 1 2 1 1 1

Windows 98 Second Editon 32bit(Virtual Box上の仮想マシーン)
Windows 2000 Professional Service Pack 4 32bit(Virtual Box上の仮想マシーン)
Windows XP Professional Service Pack 3 32bit(Virtual Box上の仮想マシーン)
Windows VISTA Ulitimate Service Pack 2 32bit
Windows 7 Enterprise Service Pack 1 64bit
Windows 8.1 Enterprise1 64bit
Windows Server 2012 Foundation
Windows 10 Pro Technical Preview Build 9926 64bit(Virtual Box上の仮想マシーン)
Windows 10 Pro Technical Preview Build 10041 64bit(Virtual Box上の仮想マシーン)
Windows 10 Pro Technical Preview Build 10061 64bit(Virtual Box上の仮想マシーン)

プログラムソースの概要

osver4.cpp

_tWinMain

英語版でもメッセージが読めるようにまず、GetOEMCP APIでコードページを取得します。
なぜ英語版を対象としているかと言うと評価版で手に入るWindows 2003R2が英語版しかないからです。
日本語版の場合は932が返ってくるので日本語メッセージをそれ以外の場合は英語メッセージを設定します。
後述するOSVERSIONINFOEXを拡張したクラスであるEXTOSVERSIONINFOEXオブジェクトを作成します。
EXTOSVERSIONINFOEX::get()を呼び出し、バージョン情報を取得します。
EXTOSVERSIONINFOEX::getOsname()を呼び出し、Windows名(7 vista等)を取得します。Windows名が不明のときはNULLが返されます。
NULLが返されたときは、unknownまたは不明という名称に設定します。
_stprintf_s関数で取得されたバージョン情報から表示する文字列を作成し、MessageBox APIを呼び出します。

extosversion.h extosversion.cpp

OSVERSIONINFOEXを拡張したクラスであるEXTOSVERSIONINFOEXの定義およびWindows名をあらわしたマクロの定義、Visual C++ 2005/2008で定義されていないSM_SERVERR2を定義しています。

EXTOSVERSIONINFOEXクラス

OSVERSIONINFOEX構造体を基本クラスとしていくつかのメンバー変数・関数を追加しています。
したがって、クラスへのポインタをOSVERSIONINFOEX*へキャストすればOSVERSIONINFOEXを対象としたAPIがそのまま使えます。
メンバー変数は、GetVersionEx,VerifyVersionInfo等のAPIへのエントリポイントやWindows 2003とWindows 2003R2を判別するためのsm2があります。
EXTOSVERSIONINFOEX() //コンストラクタ
EXTOSVERSIONINFOEXが作成されたときに呼び出されます。
まず、クラスのメンバーをZeroMemory APIにより全部0でクリアします。
GetVersionExのAPIがサポートされていないWindowsを想定し、DLL内のAPIへのエントリポイントをGetProcAddress APIで取得してみます。
マルチバイトとUNICODEでAPI名が異なる場合は、UNICODEマクロを見て必要なAPI名を設定します。
エントリポイントが0の場合は、そのAPIはサポートされていないことになります。
直接APIを記述しないメリットは、コンパイラの警告に引っかからないこと、サポートされていないAPI名が直接記述されているとサポートされていないWindows上で実行した場合、門前払いされ強制終了されますが、エントリポイントを使えば、違うAPIで対応するなど実行時に柔軟な対応ができることです。
get() //Windows Versionを取得する
GetVersionExでバージョン情報を取得します。
RtlGetVersionが使用できる場合はGetVersion2関数を呼び出します。 VerifyVersionInfo APIが使える(Windows 2000以上)場合は、GetSystemMetrics APIでWindows 2003とWindows 2003R2を判別するための情報を取得した後、
Is系のメンバー関数
IsVerifyVersionInfo() // VerifyVersionInfo APIが実装されている場合trueを返す
IsGetVersionEx() // GetVersionEx APIが実装されている場合trueを返す
Is9x() // Windows 9x系の場合 trueが返る(本関数の実行前にget()関数によりバージョン情報を取得する必要があります)
IsNT() // Windows NT 4系の場合 trueが返る(本関数の実行前にget()関数によりバージョン情報を取得する必要があります)
IsServer() // Windows Serverの場合 trueが返る(本関数の実行前にget()関数によりバージョン情報を取得する必要があります)
getOSname()
バージョン情報より一致するWindows名(7 Vista等)を示す文字列定数を返します。
Window名が見つからない場合はNULLを返します。
バージョン番号とWindows名との対比はVER_TBL ver_tbl[]で定義されている内容に従って行います。
VER_TBL型はバージョン情報とWindows名を定義した型です。
ソースを見やすくするためにメンバー変数の順番がアライメントの面から非効率的になっています。
例えば、メンバー変数をTCHAR*,DWORD,WORD,BYTEの順番に定義したほうが構造体のサイズが小さくなります。
これは実行速度を優先して、例えば32bit値なら32bitの境界にそろうようにアドレスが決定されるからです。
WORD,DWORDの順に定義するとWORD,DWORDの間に1個のWORDが挿入され位置調整がされ構造体サイズが2byte増えます。ただし構造体も効率のいい境界に配置されるので、結果的にメモリサイズは増えないかもしれません。
メンバ変数のwProductTypeが2以上の場合は2とみなしてver_tbl[]と比較します。これはサーバOSの場合、wProductTypeが2又は3であるためです。
set(TCHAR* )
引数で指定されたWindows名(7 Vista等)に一致するバージョン情報を本クラスのメンバー変数に設定します。
Windows XP相当のバージョン情報を設定する例

EXTOSVERSIONINFOEX os;
os.set(OSNAME_XP);
上記のOSNAME_XPはextosversion.hで定義されているマクロで実体は、_TEXT("XP")と定義されています。

GetVersion2

ntdll.dllをロードし、RtlGetVersion APIのエントリーポイントを取得しAPIを呼び出します。
このAPIはUNICODEのみサポートしているので、マルチバイトでコンパイルする場合は、UNICODEのOSVERINFOWEX型の変数を定義しAPIを呼び出し、取得結果をOSVERINFOEXに変換します。
UNICODEからマルチバイトへの変換は、サービスパック名のみですので、半角英数及び半角数字のみと仮定できるので、APIを使用せずに単純に上位バイトを切り捨てる処理で対応しています。

ソースコード

osver4.cpp


//      RtlGetVersion APIを使用してWindowsのバージョン番号を取得する
//      RtlGetVersion APIをサポートしていない場合は、GetVersionEx APIを使用する
//      Visual C++ 2005/2008/2013 Unicode/マルチバイト

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "extosversion.h"

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,LPTSTR lpsCmdLine, int nCmdShow){
        TCHAR buf[256];
        TCHAR* osname;
        TCHAR* unknown;
        TCHAR* support;
        TCHAR* notsupport;

        UINT cp=GetOEMCP();     //      コードページを取得
        if(cp==932){    //      日本語版の場合
                 unknown=_TEXT("不明");
                 support=_TEXT("サポート");
                 notsupport=_TEXT("未サポート");
        }else{  //      日本語版以外は、英語で表示
                unknown=_TEXT("unknown");
                support=_TEXT("support");
                notsupport=_TEXT("not support");
        }
        EXTOSVERSIONINFOEX os;
        os.get();       //      Windowsバージョンの取得
        osname=os.getOSname();  //      Windows名(7 vistaなど)の取得
        if(osname==0){
                osname = unknown;
        }
        _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("Windows %s\nMajorVersion %d\nMinorVersion %d\nBuild %d\n")
                _TEXT("ServicePack %d\nProductType 0x%.2x\nPlatformId 0x%.8x\nCSDVersion %s\nRtlGetVersion API %s\nGetVersionEX API %s"),
                osname ,os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.wServicePackMajor, os.wProductType,os.dwPlatformId,os.szCSDVersion,
                os.RtlGetVersion_api==true ? support : notsupport ,
                os.IsGetVersionEx()==true ? support : notsupport );

        MessageBox(0,buf,_TEXT("Windows Version"),MB_OK);
        return (int)0;
}

extosversion.h


#ifndef EXTOSVERSION_H

#define EXTOSVERSION_H 2

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#ifndef SM_SERVERR2
        #define SM_SERVERR2                             89
#endif

//      DLL内の関数へのポインタ型を定義
typedef BOOL (WINAPI *GetVersionExFunc)(LPOSVERSIONINFOEX );
typedef void (WINAPI *RtlGetVersion_FUNC)(OSVERSIONINFOEXW* );

BOOL GetVersion2(OSVERSIONINFOEX* os);


//      拡張版OSVERSIONINFOEXクラス

class EXTOSVERSIONINFOEX : public OSVERSIONINFOEX{
        GetVersionExFunc pGetVersionEx;
public:
        bool sm2;       //      Server R2の場合true
        bool RtlGetVersion_api;
        bool IsGetVersionEx(void){      //      APIが実装されている場合trueを返す
                return pGetVersionEx ? true : false;
        }
        bool Is9x(void){        //      Windows 9x系の場合 trueが返る
                if(dwMajorVersion ==4 && dwPlatformId==VER_PLATFORM_WIN32_WINDOWS) // Windows 9x
                        return true;
                else
                        return false;
        }
        bool IsNT(void){        //      Windows NTの場合 trueが返る
                if(dwMajorVersion ==4 && dwPlatformId==VER_PLATFORM_WIN32_NT) // Windows NT
                        return true;
                else
                        return false;
        }
        bool IsServer(void){    //      Windows Serverの場合 trueが返る
                return wProductType==VER_NT_SERVER ? true : false;
        }
        EXTOSVERSIONINFOEX(){   //      APIが実装されていない場合を想定してDLL内の関数へのエントリを取得する
                ZeroMemory(this,sizeof(OSVERSIONINFOEX));
                #ifdef UNICODE
                        pGetVersionEx = (GetVersionExFunc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetVersionExW");
                #else
                        pGetVersionEx = (GetVersionExFunc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetVersionExA");
                #endif
                RtlGetVersion_api = false;
        }
        void get(void); //      Windows Versionを取得する
        void set(TCHAR* osname);        // osnameで指定されたWindows Versionをクラスに設定します        
        TCHAR* getOSname(void); //      WindowsのOS名を取得する(7やVistaなど)
};

#define OSNAME_NT4  _TEXT("NT4")
#define OSNAME_95  _TEXT("95")
#define OSNAME_95OSR1  _TEXT("95OSR1")
#define OSNAME_95OSR2  _TEXT("95OSR2")
#define OSNAME_95OSR25  _TEXT("95OSR2.5")
#define OSNAME_98  _TEXT("98")
#define OSNAME_98SE  _TEXT("98Second Edition")
#define OSNAME_ME  _TEXT("me")
#define OSNAME_2000  _TEXT("2000")
#define OSNAME_2000_SERVER  _TEXT("2000 Server")
#define OSNAME_XP  _TEXT("XP")
#define OSNAME_SERVER_2003  _TEXT("Server 2003")
#define OSNAME_SERVER_2003R2  _TEXT("Server 2003R2")
#define OSNAME_VISTA  _TEXT("Vista")
#define OSNAME_7  _TEXT("7")
#define OSNAME_8  _TEXT("8")
#define OSNAME_81  _TEXT("8.1")
#define OSNAME_SERVER_2008  _TEXT("Server 2008")
#define OSNAME_SERVER_2008R2  _TEXT("Server 2008R2")
#define OSNAME_SERVER_2012  _TEXT("Server 2012")
#define OSNAME_SERVER_2012R2  _TEXT("Server 2012R2")
#define OSNAME_WIN10_TP9841  _TEXT("10 Technical Preview Build 9841")
#define OSNAME_WIN10_TP9860  _TEXT("10 Technical Preview Build 9860")
#define OSNAME_WIN10_TP9879  _TEXT("10 Technical Preview Build 9879")
#define OSNAME_WIN10_TP9926  _TEXT("10 Technical Preview Build 9926")
#define OSNAME_WIN10_TP10041  _TEXT("10 Technical Preview Build 10041")


#endif

extosversion.cpp


/*
 Windowsバージョンを取得するためのクラス等を定義

  将来的にGetVersionEx APIが廃止(Windows 8.1では将来的に廃止が予告されている)されてもGetVersionEx API相当のバージョン情報取得をサポート
  RtlGetVersion APIを使用(Windows 2000未満ではGetversionExを使用)
 マニフェストで実行環境をWindows 8.1に設定しなくても良いのでWindows 9x系もサポート
 バージョン情報よりWindowsの名称(Vista,7 等)の取得が可能。又はWindowsの名称からバージョン情報の設定が可能
  Windows 9x系での実行を考慮する場合はVisual C++ 2005でマルチバイトでコンパイルしなければならない。
*/


#include "extosversion.h"


#if EXTOSVERSION_H!=2
        #pragma  message ("ヘッダーファイルと本ソースのバージョンが一致していません")
#endif

#if _MSC_VER>=1800   //      Visual C++ 2013以上の場合
#include <VersionHelpers.h>
#endif


struct VER_TBL{ //      バージョン番号とOS名との一覧
        DWORD major, minor;
        DWORD platform;
        BYTE  product;
        DWORD bulid;
        WORD  sp;
        struct {
                BYTE win9x:2;   //      OSRの区別又は98と98SEの判別に使用
                BYTE server2:1; //      2003と2003R2の判別に使用
                BYTE rev:5;     //      予約済みというなの単なるビットの穴埋め
        };
        TCHAR* name;
};

VER_TBL ver_tbl[] = {
/*
         m   m  p  p  b s  w  s r   n
         a   i  l  r  u p  i  e e   a
         j   n  a  o  i    n  r v   m
         o   r  t  d  l    9  v     e
         r   r  f  c  d    x  e
                o  u          r
                r  t          2
                m
*/
        { 4, 0, 2, 0, 0,0,{ 0,0,0 }, OSNAME_NT4 },
        { 4, 0, 1, 0, 0,0,{ 0,0,0 }, OSNAME_95 },
        { 4, 0, 1, 0, 0,0,{ 0,1,0 }, OSNAME_95OSR1 },
        { 4, 0, 1, 0, 0,0,{ 0,2,0 }, OSNAME_95OSR2 },
        { 4, 0, 1, 0, 0,0,{ 0,3,0 }, OSNAME_95OSR25 },
        { 4,10, 1, 0, 0,0,{ 0,0,0 }, OSNAME_98 },
        { 4,10, 1, 0, 0,0,{ 1,0,0 }, OSNAME_98SE },
        { 4,90, 1, 0, 0,0,{ 0,0,0 }, OSNAME_ME },
        { 5, 0, 2, 1, 0,0,{ 0,0,0 }, OSNAME_2000 },
        { 5, 0, 2, 3, 0,0,{ 0,0,0 }, OSNAME_2000_SERVER },
        { 5, 1, 2, 1, 0,0,{ 0,0,0 }, OSNAME_XP },
        { 5, 2, 2, 3, 0,0,{ 0,0,0 }, OSNAME_SERVER_2003 },
        { 5, 2, 2, 3, 0,0,{ 0,1,0 }, OSNAME_SERVER_2003R2 },
        { 6, 0, 2, 1, 0,0,{ 0,0,0 }, OSNAME_VISTA },
        { 6, 1, 2, 1, 0,0,{ 0,0,0 }, OSNAME_7 },
        { 6, 2, 2, 1, 0,0,{ 0,0,0 }, OSNAME_8 },
        { 6, 3, 2, 1, 0,0,{ 0,0,0 }, OSNAME_81 },
        { 6, 0, 2, 3, 0,0,{ 0,0,0 }, OSNAME_SERVER_2008 },
        { 6, 1, 2, 3, 0,0,{ 0,0,0 }, OSNAME_SERVER_2008R2 },
        { 6, 2, 2, 3, 0,0,{ 0,0,0 }, OSNAME_SERVER_2012 },
        { 6, 3, 2, 3, 0,0,{ 0,0,0 }, OSNAME_SERVER_2012R2 },
        { 6, 4, 2, 1, 9841,0,{ 0,0,0 }, OSNAME_WIN10_TP9841 },
        { 6, 4, 2, 1, 9860,0,{ 0,0,0 }, OSNAME_WIN10_TP9860 },
        { 6, 4, 2, 1, 9879,0,{ 0,0,0 }, OSNAME_WIN10_TP9879 },
        {10, 0, 2, 1, 9926,0,{ 0,0,0 }, OSNAME_WIN10_TP9926 },
        {10, 0, 2, 1,10041,0,{ 0,0,0 }, OSNAME_WIN10_TP10041 },
        { 0, 0, 0, 0, 0,0,{ 0,0,0 }, 0}};


//      Windows Versionを取得する(GetVersionEx API相当の情報が取得できる)

void EXTOSVERSIONINFOEX::get(void){
        sm2=0;
        dwOSVersionInfoSize=sizeof(OSVERSIONINFOEX);
        LPOSVERSIONINFOEX lp=(LPOSVERSIONINFOEX)this;
        if(pGetVersionEx(lp)==0){
                dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
                pGetVersionEx(lp);
        }

        BOOL f=GetVersion2((OSVERSIONINFOEX*)this);
        if(f==TRUE){    //      VerifyVersionInfo APIをサポートしている場合 Window2000以降だがWindows2000はGetVersionEx APIで取得
                RtlGetVersion_api = true;
                dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
                sm2=GetSystemMetrics(SM_SERVERR2) ? 1 : 0;
                if(wServicePackMajor==0)
                        szCSDVersion[0]=_T('\0');
        }
}
//      WindowsのOS名を取得する(7やVistaなど)

TCHAR* EXTOSVERSIONINFOEX::getOSname(void ){
        int n = 0;
        DWORD p=wProductType;
        while (ver_tbl[n].major){
                DWORD bp=ver_tbl[n].product;
                bool f;
                if(p==2 && bp>=p)
                        f=true;
                else if(p==3 && bp==3)
                        f=true;
                else if(p==1 && bp==1)
                        f=true;
                else if(p==0 && bp==0)
                        f=true;
                else
                        f=false;

                if (ver_tbl[n].major == dwMajorVersion &&
                        ver_tbl[n].minor == dwMinorVersion &&
                        f==true &&
                        ver_tbl[n].platform== dwPlatformId &&
                        ver_tbl[n].server2 == sm2 )
                {
                        if(Is9x()){ // Windows 9xの場合
                                int win9x=0;
                                if(szCSDVersion[0] && szCSDVersion[2]){
                                        win9x=szCSDVersion[1]-_TEXT('A')+1;
                                }
                                if(ver_tbl[n].win9x==win9x){
                                        return ver_tbl[n].name;
                                }
                        }else{
                                if(ver_tbl[n].bulid==0)
                                        return ver_tbl[n].name;
                                else{
                                        if(ver_tbl[n].bulid==dwBuildNumber)
                                                return ver_tbl[n].name;
                                }
                        }
                }
                ++n;
        }
        return 0;
}

//      osnameで指定されたWindows Versionをクラスに設定します

void EXTOSVERSIONINFOEX::set(TCHAR* osname){
        int n = 0;
        while (ver_tbl[n].major){
                if(_tcscmp(ver_tbl[n].name,osname)==0){
                        this->dwMajorVersion=ver_tbl[n].major;
                        this->dwMinorVersion=ver_tbl[n].minor;
                        this->dwPlatformId=ver_tbl[n].platform;
                        this->wProductType=ver_tbl[n].product;
                        this->sm2=ver_tbl[n].server2;
                        this->dwBuildNumber=ver_tbl[n].bulid;
                        this->wServicePackMajor=ver_tbl[n].sp;
                        this->wServicePackMinor=0;
                        szCSDVersion[0]=_T('\0');
                        if(wServicePackMajor)
                                _stprintf_s(szCSDVersion,sizeof(szCSDVersion)/sizeof(TCHAR),_TEXT("Service Pack %d"),wServicePackMajor);
                        if(Is9x()){
                                if(ver_tbl[n].win9x)
                                        _stprintf_s(szCSDVersion,sizeof(szCSDVersion)/sizeof(TCHAR),_TEXT(" %c"),_T('A')+ver_tbl[n].win9x-1);
                        }
                        return;
                }
                ++n;
        }
}

BOOL GetVersion2(OSVERSIONINFOEX* os){
        HMODULE hMod;
        RtlGetVersion_FUNC func;
#ifdef UNICODE
        OSVERSIONINFOEXW* osw=os;
#else
        OSVERSIONINFOEXW o;
        OSVERSIONINFOEXW* osw=&o;
#endif

        hMod= LoadLibrary(TEXT("ntdll.dll"));
        if(hMod){
                func=(RtlGetVersion_FUNC)GetProcAddress(hMod,"RtlGetVersion");
                if(func==0){
                        FreeLibrary(hMod);
                        return FALSE;
                }
                ZeroMemory(osw,sizeof(*osw));
                osw->dwOSVersionInfoSize=sizeof(*osw);
                func(osw);
#ifndef UNICODE
                os->dwBuildNumber=osw->dwBuildNumber;
                os->dwMajorVersion=osw->dwMajorVersion;
                os->dwMinorVersion=osw->dwMinorVersion;
                os->dwPlatformId=osw->dwPlatformId;
                os->dwOSVersionInfoSize=sizeof(*os);
                DWORD sz=sizeof(os->szCSDVersion);
                WCHAR* src=osw->szCSDVersion;
                unsigned char* dtc=(unsigned char*)os->szCSDVersion;
                while(*src)
                        *dtc++ =(unsigned char) *src++;
                *dtc='\0';
#endif

        }else
                return FALSE;
        FreeLibrary(hMod);
        return TRUE;
}

実行ファイル