[C++] [WMI] Windows Storage Management

CreateFIle API を使うとき、多くの場合では第一引数にファイル パスを渡してファイルを開きます。CreateFile の処理は内部的に Windows カーネルのオブジェクト マネージャーが第一引数の文字列をデバイス オブジェクトとして解釈し、I/O マネージャーが適当なドライバーにリダイレクトするという流れになっています。例えば、指定したファイルのあるボリュームが NTFS フォーマットだったら ntfs.sys が処理し、UNC パスだったら mrxsmb.sys が処理したりという感じです。厳密には誤りがあるかもしれませんが、大枠はこんな感じです。

CreateFile は、ファイル システムだけでなくデバイスへ直接アクセスすることもできます。ボリュームやディスクへのダイレクト I/O を行なえるわけです。シリアル ケーブルの通信なんかもそうですね。詳しくは MSDN で CreateFile を見て下さい。

CreateFile function
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx

ダイレクト I/O を使うと、フォーマットされていないボリュームや、パーティショニングされていないディスクに対して読み書きが可能になります。いわゆる RAW ディスクというやつで、ファイル システム ドライバーを必要としないわけです。この場合、上記 MSDN ページに書いてありますが CreateFile の第一引数に \\.\C: とか \\.\PhysicalDrive0 というような文字列を渡します。前者がボリューム デバイスを開く場合で、後者がディスク デバイスを開く場合です。

この文字列は、デバイス オブジェクトを直接示しているのではなく、デバイス オブジェクトへのシンボリック リンクの名前を示しています。デバイス オブジェクトについては、お馴染み Winobj で見ることができます。

image

Winobj
http://technet.microsoft.com/en-us/sysinternals/bb896657

上の画面だと、例えば以下のようなシンボリック リンクを確認できます。

\GLOBAL??\C: ⇒ \Device\HarddiskVolume1
\GLOBAL??\PhysicalDrive0 ⇒ \Device\Harddisk0\DR0

\GLOBAL?? というのは、オブジェクト マネージャーの解釈する名前空間です。が、Windows API はこの名前空間を解釈することができません。このため、\\.\ という接頭辞をつけるルールになっていて、CreateFile はこれを \GLOBAL?? という名前空間に変換してオブジェクト マネージャーに渡します。上の例で行けば、以下のような流れがあるわけです。

\\.\PhysicalDrive0 ⇒ \GLOBAL??\PhysicalDrive0 ⇒ \Device\Harddisk0\DR0

オブジェクト マネージャーが管理するオブジェクトについては、カーネル デバッガーの !object コマンドでも確認可能です。上記画面キャプチャと同じ環境でのカーネル デバッガーの出力を抜粋します。OS は Windows 7 SP1 64bit です。

まずは C: について。シンボリック リンクが HarddiskVolume0 を指していて、HarddiskVolume0 は volmgr.sys のデバイス オブジェクトであることが分かります。

0: kd> !driveinfo c:
Drive c:, DriveObject fffff8a00029c420
Directory Object: fffff8a000008060 Name: C:
Target String is ‘\Device\HarddiskVolume1’
Drive Letter Index is 3 (C:)
Volume DevObj: fffffa8007cc39a0
Vpb: fffffa8007cc0820 DeviceObject: fffffa8007fd0030
FileSystem: \FileSystem\Ntfs
*************************************************************************
*** ***
*** ***
*** Your debugger is not using the correct symbols ***
*** ***
*** In order for this command to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: ntfs!VOLUME_DEVICE_OBJECT ***
*** ***
*************************************************************************
Cannot get ntfs!VOLUME_DEVICE_OBJECT.Vcb @ fffffa8007fd0030

0: kd> !object \GLOBAL??\C:
Object: fffff8a00029c420  Type: (fffffa8006ca8de0) SymbolicLink
    ObjectHeader: fffff8a00029c3f0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: fffff8a000008060  Name: C:
    Target String is ‘\Device\HarddiskVolume1’
    Drive Letter Index is 3 (C:)

0: kd> !object \device\harddisk1\partition1
Object: fffff8a00029cd10  Type: (fffffa8006ca8de0) SymbolicLink
    ObjectHeader: fffff8a00029cce0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: fffff8a0001f8060  Name: Partition1
    Target String is ‘\Device\HarddiskVolume1’

0: kd> !object \Device\HarddiskVolume1
Object: fffffa8007cc39a0  Type: (fffffa8006d32c90) Device
    ObjectHeader: fffffa8007cc3970 (new version)
    HandleCount: 0  PointerCount: 9
    Directory Object: fffff8a000010920  Name: HarddiskVolume1

0: kd> !devobj fffffa8007cc39a0
Device object (fffffa8007cc39a0) is for:
HarddiskVolume1*** ERROR: Module load completed but symbols could not be loaded for spgu.sys
\Driver\volmgr DriverObject fffffa8007b617c0
Current Irp 00000000 RefCount 30480 Type 00000007 Flags 00201150
Vpb fffffa8007cc0820 Dacl fffff9a10033f0d0 DevExt fffffa8007cc3af0 DevObjExt fffffa8007cc3c58 Dope fffffa8007cc4820 DevNode fffffa8007ccda90
ExtensionFlags (0x00000800)
                             Unknown flags 0x00000800
AttachedDevice (Upper) fffffa8007ccea40 \Driver\fvevol
Device queue is not busy.

次に PhysicalDrive0 について。
PhysicalDrive0 が \Device\Harddisk0\DR0 という disk.sys のデバイス オブジェクトにリンクしていることが分かります。この DR0 というのがディスクのデバイス オブジェクトになるわけですが、NT4 の命名規約との互換性のためか、\Device\Harddisk0\Partition0 というシンボリック リンクも DR0 にリンクしています。このため、パーティション番号は 1 から始まることになります。DR が何の略なのかちょっと調べましたが出てきませんでした。知っている人教えて下さいー。

0: kd> !object \GLOBAL??\PhysicalDrive0
Object: fffff8a000154fe0  Type: (fffffa8006ca8de0) SymbolicLink
    ObjectHeader: fffff8a000154fb0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: fffff8a000008060  Name: PhysicalDrive0
    Target String is ‘\Device\Harddisk0\DR0’

0: kd> !object \Device\Harddisk0\DR0
Object: fffffa8007e7c060  Type: (fffffa8006d32c90) Device
    ObjectHeader: fffffa8007e7c030 (new version)
    HandleCount: 0  PointerCount: 4
    Directory Object: fffff8a0001f8eb0  Name: DR0

0: kd> !devobj fffffa8007e7c060
Device object (fffffa8007e7c060) is for:
DR0 \Driver\Disk DriverObject fffffa8007cbce70
Current Irp 00000000 RefCount 0 Type 00000007 Flags 01002050
Vpb fffffa8007cbdb80 Dacl fffff9a100303ae0 DevExt fffffa8007e7c1b0 DevObjExt fffffa8007e7c858 Dope fffffa8007cbdb10
ExtensionFlags (0x00000800)
                             Unknown flags 0x00000800
AttachedDevice (Upper) fffffa8007e7cb90 \Driver\partmgr
AttachedTo (Lower) fffffa8007abd420 \Driver\ACPI
Device queue is not busy.

0: kd> !object \Device\Harddisk0\Partition0
Object: fffff8a000154200  Type: (fffffa8006ca8de0) SymbolicLink
    ObjectHeader: fffff8a0001541d0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: fffff8a0001f8eb0  Name: Partition0
    Target String is ‘\Device\Harddisk0\DR0’

なお、この辺の動作は「インサイド Windows 第四版 第 10 章 ストレージ管理」 に書いてあります。

ダイレクト I/O 周りの動作をデバッグしてもいいのですが、それは今後検討するとして、今回はシンボリック リンク名とドライブ文字のマッピングについてのプログラムを書きました。例えば、C: ドライブは PhysicalDrive 何番なのか、逆に PhysicalDrive1 にはどのドライブ文字が割り当てられているのか、という点について調べようとすると、ビルトインでいいツールがないのです。いちいち Winobj やらデバッガーを使うのも鶏を割くのに牛刀を用いる感じがします。

プログラムはこんな感じです。
ご覧のとおり WMI を使いました。WMI だと間接的に情報を取ってくることになり、一次情報を取っていない気がして本当は嫌なのですが、他にシンプルな方法が思い浮かばなくて仕方なく、です。VDS である程度の情報は取ってこれるんですけどね。
なお、このプログラムでは CD-ROM ドライブや、ドライブ文字が割り当てられていないボリュームが出力されません。ダメダメです。

//
// wmidata.cpp
//

#include <Windows.h>
#include <stdio.h>
#include <strsafe.h>

#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

typedef struct _DRIVEINFO {
    WCHAR DriveLetter;
    UINT DiskIndex;
    UINT PartitionIndex;
} DRIVEINFO, *PDRIVEINFO;

class CWmiService {
private:
    IWbemLocator *mLocator;
    IWbemServices *mService;

public:
    CWmiService();
    ~CWmiService();

    BOOL Initialize();

    inline operator IWbemServices*() const {
        return mService;
    }

    IWbemClassObject *GetObject(IWbemClassObject*, LPCWSTR);
    UINT GetUint32(IWbemClassObject*, LPCWSTR);
    VOID GetString(IWbemClassObject*, LPCWSTR, PWSTR, ULONG);

    ULONG GetCount(LPCWSTR);

};

CWmiService::CWmiService()
    : mLocator(NULL), mService(NULL)
{}

CWmiService::~CWmiService() {
    if ( mLocator ) mLocator->Release();
    if ( mService ) mService->Release();
    CoUninitialize();
}

// http://technet.microsoft.com/ja-jp/library/aa390423(v=vs.85).aspx
BOOL CWmiService::Initialize() {
    HRESULT Result;
   
    Result= CoInitializeEx(0, COINIT_MULTITHREADED);
    if ( FAILED(Result) ) {
        wprintf(L"CoInitializeEx failed – 0x%08x\n", Result);
        return FALSE;
    }

    Result= CoInitializeSecurity(NULL, -1, NULL, NULL,
        RPC_C_AUTHN_LEVEL_DEFAULT,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL, EOAC_NONE, NULL);
    if ( FAILED(Result) ) {
        wprintf(L"CoInitializeSecurity failed – 0x%08x\n", Result);
        return FALSE;
    }
   
    Result = CoCreateInstance(CLSID_WbemLocator,
        0, CLSCTX_INPROC_SERVER,
        IID_IWbemLocator, (LPVOID *)&mLocator);
    if ( FAILED(Result) ) {
        wprintf(L"CoCreateInstance failed – 0x%08x\n", Result);
        return FALSE;
    }

    Result= mLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),
         NULL, NULL, 0, NULL, 0, 0,&mService);
    if ( FAILED(Result) ) {
        wprintf(L"IWbemLocator::ConnectServer failed – 0x%08x\n", Result);
        return FALSE;
    }

    Result= CoSetProxyBlanket(mService,
       RPC_C_AUTHN_WINNT,
       RPC_C_AUTHZ_NONE,
       NULL,
       RPC_C_AUTHN_LEVEL_CALL,
       RPC_C_IMP_LEVEL_IMPERSONATE,
       NULL, EOAC_NONE);
    if ( FAILED(Result) ) {
        wprintf(L"CoSetProxyBlanket failed – 0x%08x\n", Result);
        return FALSE;
    }

    return TRUE;
}

IWbemClassObject *CWmiService::GetObject(IWbemClassObject *Object, LPCWSTR Property) {
    HRESULT Result;
    IWbemClassObject *Ret= NULL;
    VARIANT Value;

    Result= Object->Get(Property, 0, &Value, NULL, NULL);
    if ( FAILED(Result) ) {
        wprintf(L"IWbemClassObject::Get failed – 0x%08x\n", Result);
        return NULL;
    }

    Result= mService->GetObject(Value.bstrVal,
        WBEM_FLAG_RETURN_WBEM_COMPLETE,
        NULL, &Ret, NULL);
    if ( FAILED(Result) ) {
        wprintf(L"IWbemServices::GetObject failed – 0x%08x\n", Result);
        Ret= NULL;
    }

    VariantClear(&Value);

    return Ret;
}

UINT CWmiService::GetUint32(IWbemClassObject *Object, LPCWSTR Property) {
    LRESULT Result;
    UINT Ret= 0;
    VARIANT Value;

    Result= Object->Get(Property, 0, &Value, NULL, NULL);
    if ( SUCCEEDED(Result) )
        Ret= Value.uintVal;
    else
        wprintf(L"IWbemClassObject::Get failed – 0x%08x\n", Result);

    VariantClear(&Value);

    return Ret;
}

VOID CWmiService::GetString(IWbemClassObject *Object, LPCWSTR Property,
                            PWSTR Buffer, ULONG BufferLength) {
    LRESULT Result;
    VARIANT Value;

    Result= Object->Get(Property, 0, &Value, NULL, NULL);
    if ( SUCCEEDED(Result) ) {
        StringCchCopy(Buffer, BufferLength, Value.bstrVal);
        VariantClear(&Value);
    }
    else {
        wprintf(L"IWbemClassObject::Get failed – 0x%08x\n", Result);
        return;
    }
}

ULONG CWmiService::GetCount(LPCWSTR Query) {
    LRESULT Result;
    ULONG Ret= 0;
    CONST INT BatchCount= 100;
    IWbemClassObject *Object[BatchCount];
    IEnumWbemClassObject *Enumerator= NULL;
   
    Result= mService->ExecQuery(bstr_t("WQL"), bstr_t(Query),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL, &Enumerator);
    if ( FAILED(Result) ) {
        wprintf(L"IWbemServices::ExecQuery failed – 0x%08x\n", Result);
        return 0;
    }

    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa394175(v=vs.85).aspx
    while ( Enumerator ) {
        ULONG Count= 0;
        Result= Enumerator->Next(WBEM_INFINITE, BatchCount, Object, &Count);
        if ( Count==0 ) break;
        if ( FAILED(Result) ) {
            wprintf(L"IEnumWbemClassObject::Next failed – 0x%08x\n", Result);
            break;
        }
        Ret+= Count;

        for ( ULONG i=0 ; i<Count ; ++i )
            Object[i]->Release();
    }

    Enumerator->Release();

    return Ret;
}

typedef int (__cdecl *PCOMPAREFUNC)(const void *, const void *);

int CompareDriveInfo_Index(CONST PDRIVEINFO p1, CONST PDRIVEINFO p2) {
    if ( p1->DiskIndex==p2->DiskIndex )
        return p1->PartitionIndex-p2->PartitionIndex;
    else
        return p1->DiskIndex-p2->DiskIndex;
}

int CompareDriveInfo_Drive(CONST PDRIVEINFO p1, CONST PDRIVEINFO p2) {
    return p1->DriveLetter-p2->DriveLetter;
}

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa394175(v=vs.85).aspx
VOID DumpDiskDriveMapping() {
    CONST WCHAR QUERY_MAPPING[]= L"SELECT * FROM Win32_LogicalDiskToPartition";
    LRESULT Result;
    ULONG Count= 0;
    IEnumWbemClassObject *Enumerator= NULL;
    IWbemClassObject **Object= NULL;
    IWbemClassObject *Partition= NULL; // Antecedent
    IWbemClassObject *LogicalDisk= NULL; // Dependent
    PDRIVEINFO DriveInfo= NULL;
    ULONG i;

    CWmiService wmi;
    wmi.Initialize();
   
    ULONG MappedDrives= wmi.GetCount(QUERY_MAPPING);
    if ( MappedDrives==0 ) {
        wprintf(L"No drives?\n");
        goto cleanup;
    }

    DriveInfo= new DRIVEINFO[MappedDrives];
    Object= new IWbemClassObject*[MappedDrives];
    if ( !DriveInfo || !Object ) {
        wprintf(L"Memory allocation error – 0x%08x\n", GetLastError());
        goto cleanup;
    }

    Result= ((IWbemServices*)wmi)->ExecQuery(
        bstr_t("WQL"), bstr_t(QUERY_MAPPING),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL, &Enumerator);
    if ( FAILED(Result) ) {
        wprintf(L"IWbemServices::ExecQuery failed – 0x%08x\n", Result);
        goto cleanup;
    }

    Result= Enumerator->Next(WBEM_INFINITE, MappedDrives, Object, &Count);
    if ( FAILED(Result) ) {
        wprintf(L"IEnumWbemClassObject::Next failed – 0x%08x\n", Result);
        goto cleanup;
    }
   
    for ( i=0 ; i<Count ; ++i ) {
        Partition= wmi.GetObject(Object[i], L"Antecedent");
        if ( Partition ) {
            //
http://msdn.microsoft.com/en-us/library/windows/desktop/aa394135(v=vs.85).aspx
            DriveInfo[i].PartitionIndex= wmi.GetUint32(Partition, L"Index");
            DriveInfo[i].DiskIndex= wmi.GetUint32(Partition, L"DiskIndex");
            Partition->Release();
        }

        LogicalDisk= wmi.GetObject(Object[i], L"Dependent");
        if ( LogicalDisk ) {
            //
http://msdn.microsoft.com/en-us/library/windows/desktop/aa394173(v=vs.85).aspx
            WCHAR Drive[3];
            wmi.GetString(LogicalDisk, L"DeviceID", Drive, 2);
            DriveInfo[i].DriveLetter= Drive[0];
            LogicalDisk->Release();
        }

        Object[i]->Release();
    }

    qsort(DriveInfo, Count, sizeof(DRIVEINFO), (PCOMPAREFUNC)CompareDriveInfo_Index);

    // http://otndnld.oracle.co.jp/document/products/oracle10g/102/windows/B25695-02/ap_raw.htm

    int diskindex= -1;
    for ( i=0 ; i<Count ; ++i ) {
        if ( diskindex!=DriveInfo[i].DiskIndex ) {
            wprintf(L"\n\\\\.\\PhysicalDrive%d\n", DriveInfo[i].DiskIndex);
            diskindex= DriveInfo[i].DiskIndex;
        }

        wprintf(L"  \\\\.\\%c: => \\Device\\Harddisk%d\\Partition%d\n",
            DriveInfo[i].DriveLetter,
            DriveInfo[i].DiskIndex,
            DriveInfo[i].PartitionIndex+1,
            DriveInfo[i].DiskIndex);
    }

    //putwchar(L’\n’);
    //qsort(DriveInfo, Count, sizeof(DRIVEINFO), (PCOMPAREFUNC)CompareDriveInfo_Drive);
    //
    //for ( i=0 ; i<Count ; ++i ) {
    //    wprintf(L"  %c: => \\Device\\Harddisk%d\\Partition%d : \\PhysicalDrive%d\n",
    //        DriveInfo[i].DriveLetter,
    //        DriveInfo[i].DiskIndex,
    //        DriveInfo[i].PartitionIndex+1,
    //        DriveInfo[i].DiskIndex);
    //}

cleanup:
    if ( Object ) delete [] Object;
    if ( DriveInfo ) delete [] DriveInfo;
    if ( Enumerator ) Enumerator->Release();

}

出力結果はこんな感じになります。

出力例 1.

\\.\PhysicalDrive0
  \\.\V: => \Device\Harddisk0\Partition2

\\.\PhysicalDrive1
  \\.\C: => \Device\Harddisk1\Partition1
  \\.\H: => \Device\Harddisk1\Partition2
  \\.\T: => \Device\Harddisk1\Partition3

\\.\PhysicalDrive2
  \\.\E: => \Device\Harddisk2\Partition1
  \\.\W: => \Device\Harddisk2\Partition2

\\.\PhysicalDrive3
  \\.\F: => \Device\Harddisk3\Partition1

出力例 2.

\\.\PhysicalDrive0
  \\.\C: => \Device\Harddisk0\Partition2
  \\.\N: => \Device\Harddisk0\Partition4
  \\.\O: => \Device\Harddisk0\Partition4

\\.\PhysicalDrive1
  \\.\E: => \Device\Harddisk1\Partition1
  \\.\F: => \Device\Harddisk1\Partition2
  \\.\G: => \Device\Harddisk1\Partition3
  \\.\H: => \Device\Harddisk1\Partition4
  \\.\I: => \Device\Harddisk1\Partition5
  \\.\J: => \Device\Harddisk1\Partition6
  \\.\K: => \Device\Harddisk1\Partition7
  \\.\L: => \Device\Harddisk1\Partition8
  \\.\M: => \Device\Harddisk1\Partition9

パーティション番号が抜けているところは、ドライブ文字が割り当てられていないボリュームです。
出力例 2. で \Device\Harddisk0\Partition4 が 2 つあるのは、これが拡張パーティションで、論理ドライブが 2 つあるという意味です。MBR の制限ですね。一方 PhysicalDrive1 は GPT パーティショニングなので、9 つのパーティションでも問題なしです。

今回は C++ プログラムで処理しましたが、WMI については、やはり PowerShell でアクセスするのが一番楽です。他にも wmic やら wbemtest、VB スクリプトを使うなどの方法もあります。しかし、Windows 7 (2008 R2) なら PowerShell の Get-WMIObject と Set-WmiInstance コマンドレットを使わない手はありません。例え起動が遅いと言われようとも、これだけのために PowerShell を使う価値はあります。

ソース中のコメントに入れてありますが、今回のプログラムは Win32_DiskDriveToDiskPartition クラスのインスタンスを適当に成形しているだけです。

PS > Get-WmiObject Win32_DiskDriveToDiskPartition | fl Antecedent,Dependent

Antecedent : \\ALANINE\root\cimv2:Win32_DiskDrive.DeviceID="\\\\.\\PHYSICALDRIVE1"
Dependent  : \\ALANINE\root\cimv2:Win32_DiskPartition.DeviceID="Disk #1, Partition #0"

Antecedent : \\ALANINE\root\cimv2:Win32_DiskDrive.DeviceID="\\\\.\\PHYSICALDRIVE1"
Dependent  : \\ALANINE\root\cimv2:Win32_DiskPartition.DeviceID="Disk #1, Partition #1"

Antecedent : \\ALANINE\root\cimv2:Win32_DiskDrive.DeviceID="\\\\.\\PHYSICALDRIVE1"
Dependent  : \\ALANINE\root\cimv2:Win32_DiskPartition.DeviceID="Disk #1, Partition #2"

Antecedent : \\ALANINE\root\cimv2:Win32_DiskDrive.DeviceID="\\\\.\\PHYSICALDRIVE0"
Dependent  : \\ALANINE\root\cimv2:Win32_DiskPartition.DeviceID="Disk #0, Partition #0"

(2015/1/8 追記)
意外とこの記事へのアクセス数が多いこともあり、3 年近く前の記事への追記です。とても便利なビルトインのツールがありました。Windows Server 2003 のサポート ツールに含まれている dmdiag.exe です。

Windows Server 2003 Service Pack 2 32-bit Support Tools
http://www.microsoft.com/en-us/download/details.aspx?id=15326

管理者権限で dmdiag.exe に -v オプションをつけて実行すると、ストレージ周りの情報をかなり詳細にダンプしてくれます。古いツールですが、少なくとも Windows 8.1 (Server 2012 R2) までなら動作します。以下、出力の抜粋です。

d:\MSWORK>.\dmdiag  -v
---------- Computer Name and OS Version ---------- 
       Computer name: GUANOSINE
            NT build: 9200
            CPU Type: x86
      DMDIAG Version: 5.2.3790.0 shp
---------- LDM File Versions ----------
(..snip..)
---------- Mount Points ----------
---------- Drive Letter Usage, Drive Type ----------
C: = \Device\HarddiskVolume6 [Fixed]
D: = \Device\HarddiskVolume8 [Fixed]
E: = \Device\HarddiskVolume4 [Fixed]
Q: = \Device\CdRom0 [CDRom]
U: = \Device\HarddiskVolume3 [Fixed]
V: = \Device\HarddiskVolume2 [Fixed]
W: = \Device\HarddiskVolume7 [Fixed]
X: = \Device\HarddiskVolume5 [Fixed]
---------- Consolidated LDM Configuration Data ----------
(..snip..)
---------- \Device\Harddisk0 ----------
\Device\Harddisk0\DR0            (Device)
\Device\Harddisk0\Partition0     (SymbolicLink) -> \Device\Harddisk0\DR0
\Device\Harddisk0\Partition1     (SymbolicLink) -> \Device\HarddiskVolume1
\Device\Harddisk0\Partition2     (SymbolicLink) -> \Device\HarddiskVolume2
---------- \Device\Harddisk1 ----------
\Device\Harddisk1\DR1            (Device)
\Device\Harddisk1\Partition0     (SymbolicLink) -> \Device\Harddisk1\DR1
\Device\Harddisk1\Partition1     (SymbolicLink) -> \Device\HarddiskVolume3
---------- \Device\Harddisk2 ----------
\Device\Harddisk2\DR2            (Device)
\Device\Harddisk2\Partition0     (SymbolicLink) -> \Device\Harddisk2\DR2
\Device\Harddisk2\Partition1     (SymbolicLink) -> \Device\HarddiskVolume4
\Device\Harddisk2\Partition2     (SymbolicLink) -> \Device\HarddiskVolume5
---------- \Device\Harddisk3 ----------
\Device\Harddisk3\DR3            (Device)
\Device\Harddisk3\Partition0     (SymbolicLink) -> \Device\Harddisk3\DR3
\Device\Harddisk3\Partition1     (SymbolicLink) -> \Device\HarddiskVolume6
\Device\Harddisk3\Partition2     (SymbolicLink) -> \Device\HarddiskVolume7
\Device\Harddisk3\Partition3     (SymbolicLink) -> \Device\HarddiskVolume8
---------- Partition Table Info Disk 0 ---------- 
          14,593 Cylinders
             255 Tracks/Cylinder
              63 Sectors/Track
             512 Bytes/Sector
              12 MediaType
     234,436,545 Sectors (total)
120,031,511,040 Bytes (total)
     117,218,273 KB
         114,471 MB
           111.8 GB

               0 StartingOffset
120,034,123,776 PartitionLength
               0 HiddenSectors
               0 PartitionNumber
               0 PartitionType
               0 BootIndicator
               0 RecognizedPartition
               0 RewritePartition

             MBR PartitionStyle
               4 PartitionCount
        3afc7bc6 Signature

        Starting        Partition     Hidden       Total  Partition    Partition       Boot Recognized    Rewrite
  Offset (bytes)   Length (bytes)    Sectors     Sectors     Number   Type (HEX)  Indicator  Partition  Partition

       1,048,576      104,857,600      2,048     204,800          0         0x07          1          1          0
     105,906,176  119,925,637,120    206,848 234,229,760          1         0x07          0          1          0
               0                0          0           0          2         0x00          0          0          0
               0                0          0           0          3         0x00          0          0          0

120,031,511,040 Bytes (234436545 sectors) Geometric size
120,034,123,776 Bytes (234441648 sectors) True size (measured)
120,034,123,776 Bytes (234441648 sectors) Reported size (Partition0)
               0 Bytes (       0 sectors) missing/wasted

---------- Partition Table Info Disk 1 ----------
(..snip..)
---------- Partition Table Info Disk 2 ----------
(..snip..)
---------- Partition Table Info Disk 3 ----------
(..snip..)
---------- DMIO Kernel List ----------
(..snip..)

[Win32] [C++] Local RPC over Named Pipe and LPC

Microsoft の分散コンピューティング関連技術は入り乱れていて把握が難しいのが現状です。その中でも外せないのが RPC (=Remote Procedure Call) でしょうか。

  • MSRPC = DCE/RPC の MS 実装
  • DCOM = MSRPC を使った COM
  • COM+ = COM + MTS
  • ローカルでの RPC では多くの場合 Windows カーネルが提供する LPC (=Local Procedure Call) が使われる

DCOM じゃない COM のメソッド呼び出しは RPC を使っているのではないかという気がしますが、ちょっと自信がないです。間違っていたら誰かコメント下さい。これもカーネルデバッグすれば分かるのでしょうが。

COM については、以下のページを参考にして下さい。

COM+ (Component Services)
http://www.microsoft.com/com/default.mspx

今回は、ローカルでの RPC を検証するサンプル プログラムを作ってみました。
RPC が対応しているプロトコルの一覧は以下のページにありますが、そのうちの名前付きパイプと LPC  を使えるようにしてあります。

Protocol Sequence Constants
http://msdn.microsoft.com/en-us/library/aa374395(VS.85).aspx

ベースは、以下のページにあるサンプル プログラムを使っています。(コピペともいう)

RPC Sample Application
http://msdn.microsoft.com/en-us/library/dd418893(v=prot.10).aspx

作る順番としては、こんな感じになるでしょうか。全部 Visual Studio からできますが、uuidgen と MIDL をコマンドラインから直接実行する方が面白いです。

  1. uuidgen で IDL ファイルのひな型を作る
  2. IDL ファイルと ACF ファイルを書く
  3. MIDL でコンパイル
  4. RPC サーバーを書く
  5. RPC クライアントを書く
  6. とりあえず動かす

1. uuidgen で IDL ファイルのひな型を作る

IDL (=Interface Definition Language) は、RPC インターフェースを定義するためのプログラミング言語で、Windows に限らず、Linux でも動くコンパイラはあるようです。

スタートメニューから、 "Visual Studio Command Prompt (2010)" を実行し、以下のコマンドを実行します。

> uuidgen /i /opipo.idl

以下のようなひな型が生成されます。要は UUID が生成されただけです。

[
uuid(6504fa96-8126-401b-adfd-18580a6e9d86),
version(1.0)
]
interface INTERFACENAME
{

}

2. IDL ファイルと ACF ファイルを書く

IDL ファイルには、RPC メソッドのスタブを C/C++ のプロトタイプ宣言として記述します。INTERFACENAME も適当なものに変えておきます。今回は 3 つのメソッドを宣言しました。

//
// pipo.idl
//
// generated with ‘uuidgen /i /opipo.idl’
// compile with ‘midl pipo.idl’
//
//
http://msdn.microsoft.com/en-us/library/aa367088
//

[
uuid(6504fa96-8126-401b-adfd-18580a6e9d86),
version(1.0)
]
interface pipo
{
    void RpcSleep(int Duration);
    void Hello([in, string] const wchar_t *String);
    void Shutdown();
}

コメントにも入れましたが、以下のページに MIDL のリファレンスがあるので、適宜参照してください。
http://msdn.microsoft.com/en-us/library/aa367088

ACF ファイルは、メモリや例外など、メソッドに関する属性を記述するのに使われるファイル、だそうです。

今回は RPC バインディング ハンドルを宣言するのに使います。これを書くことで、ハンドルを自動的に定義/宣言してくれるので楽です。

//
// pipo.acf
//
//
http://msdn.microsoft.com/en-us/library/aa366717(v=VS.85).aspx
//

[
implicit_handle(handle_t pipo_IfHandle)
]
interface pipo
{
}

3. MIDL でコンパイル

IDL と ACF ファイルをコンパイルします。 2 つのファイルを同じフォルダーにおいて、以下のコマンドを実行して下さい。

> midl pipo.idl
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0555
Copyright (c) Microsoft Corporation. All rights reserved.
Processing .\pipo.idl
pipo.idl
Processing .\pipo.acf
pipo.acf

これにより、以下 3 つのファイルが生成されますので、これから作るクライアントやサーバー プログラムのプロジェクトにコピーしておきます。

  • pipo.h – クライアント/サーバー共通のヘッダー
  • pipo_c..c – クライアント用ソース ファイル (スタブの定義など)
  • pipo_s.c – サーバー用ソース ファイル

RPC 基盤では、RPC メソッドに渡すパラメーターのメモリ領域を動的に確保/解放するための関数を必要としており、その定義はクライアント/サーバーの双方に自分で書かなけれないけません。関数名は midl_user_allocate と midl_user_free です。とはいっても、malloc と free を呼び出すだけにするのが慣習のようです。もし大きなメモリブロックが必要だったら、VirtualAlloc を使った方がいいかもしれません、たぶん。

4. RPC サーバーを書く

コマンドライン引数を処理して、指定されたプロトコルで待機するだけのプログラムです。
Shutdown は、クライアントからサーバーを終了するためのメソッドで、これを用意しておくのは慣習みたいなので実装しておきました。midl_user_allocate と midl_user_free を定義し忘れると [未解決のシンボル] エラーが出るので注意。もちろん rpcrt4.lib をリンカに追加するのも忘れないように。

//
// piposerver.cpp
//
//
http://msdn.microsoft.com/en-us/library/dd418893(v=prot.10).aspx
// http://msdn.microsoft.com/en-us/library/aa374395(VS.85).aspx
//

#include <Windows.h>
#include <stdio.h>

#include "pipo.h"

#define PROT_LPC    ((RPC_WSTR)L"ncalrpc")
#define PROT_NP     ((RPC_WSTR)L"ncacn_np")

void RpcSleep(int Duration) {
    wprintf(L"[Pipo:0x%x] Duration: %u msec…\n",
      GetCurrentThreadId(), Duration);
    Sleep(Duration);
    wprintf(L"[Pipo:0x%x] done.\n", GetCurrentThreadId());
}

void Hello(LPCWSTR String) {
    wprintf(L"[Pipo:0x%x] %s\n", GetCurrentThreadId(), String);

void Shutdown() {
    RPC_STATUS Status= RPC_S_OK;

    Status= RpcMgmtStopServerListening(NULL);
    if ( Status!=RPC_S_OK )
        wprintf(L"[Shutdown:0x%x] RpcMgmtStopServerListening failed (0x%08x)\n",
          GetCurrentThreadId(), Status);

    Status = RpcServerUnregisterIf(NULL, NULL, FALSE);
    if ( Status!=RPC_S_OK )
        wprintf(L"[Shutdown:0x%x] RpcServerUnregisterIf failed (0x%08x)\n",
          GetCurrentThreadId(), Status);

    wprintf(L"[Shutdown:0x%x] done.\n", GetCurrentThreadId());
}

void __RPC_FAR * __RPC_USER midl_user_allocate(size_t len) {
    return malloc(len);
}

void __RPC_USER midl_user_free(void __RPC_FAR * ptr) {
    free(ptr);
}

#define MAX_PROTOCOL 10
static wchar_t upperstr[MAX_PROTOCOL+1];
const wchar_t *ToUpper(const wchar_t *s) {
    for ( int i=0 ; i<MAX_PROTOCOL+1 ; ++i ) {
        upperstr[i]= toupper(s[i]);
        if ( s[i]==0 )
            return upperstr;
    }
    upperstr[MAX_PROTOCOL]= 0;
    return upperstr;
}

/*
   usage: piposerver [PIPE|LPC] [endpoint] [maxinstance]
*/

int wmain(int argc, wchar_t *argv[]) {
    if ( argc<4 ) {
        wprintf(L"\nusage: piposerver [PIPE|LPC] [endpoint] [maxinstance]\n");
        exit(ERROR_INVALID_PARAMETER);
    }
   
    LPCWSTR UpperProt= ToUpper(argv[1]);
    RPC_WSTR Protocol= NULL;
    if ( wcscmp(UpperProt, L"PIPE")==0 )
        Protocol= PROT_NP;
    else if ( wcscmp(UpperProt, L"LPC")==0 )
        Protocol= PROT_LPC;
    else {
        wprintf(L"Unknown procotol.\n");
        return ERROR_INVALID_PARAMETER;
    }

    RPC_STATUS Status= RPC_S_OK;
    RPC_WSTR Endpoint= (RPC_WSTR)argv[2];
    unsigned int MaxInstance= min(_wtoi(argv[3]),
      RPC_C_LISTEN_MAX_CALLS_DEFAULT);
   
    Status = RpcServerUseProtseqEp(Protocol, MaxInstance, Endpoint, NULL);
    if ( Status!=RPC_S_OK ) {
        wprintf(L"RpcServerUseProtseqEp failed (0x%08x)\n", Status);
        exit(Status);
    }
 
    Status= RpcServerRegisterIf(pipo_v1_0_s_ifspec, NULL, NULL);
    if (Status!=RPC_S_OK) {
        wprintf(L"RpcServerRegisterIf failed (0x%08x)\n", Status);
        exit(Status);
    }
 
    wprintf(L"Protocol:      %s\n", Protocol);
    wprintf(L"Endpoint:      %s\n", Endpoint);
    wprintf(L"Max instances: %u\n", MaxInstance);
    wprintf(L"RPC Server listeing (TID:0x%x) …\n\n", GetCurrentThreadId());

    Status = RpcServerListen(1, MaxInstance, 0);
    if (Status!=RPC_S_OK) {
        wprintf(L"RpcServerListen failed (0x%08x)\n", Status);
       
        Status= RpcServerUnregisterIf(NULL, NULL, FALSE);
        if ( Status!=RPC_S_OK )
            wprintf(L"RpcServerUnregisterIf failed (0x%08x)\n", Status);

        exit(Status);
    }

    return 0;
}

5. RPC クライアントを書く

次に RPC クライアントを書きます。こちらも単一ファイルで。
サーバーと同じく、midl_user_allocate と midl_user_free の定義と、rpcrt4.lib のリンカへの追加を忘れないように。

//
// pipoclient.cpp
//

#include <Windows.h>
#include <stdio.h>
#include <Rpc.h>

#include "pipo.h"

#define PROT_LPC    ((RPC_WSTR)L"ncalrpc")
#define PROT_NP     ((RPC_WSTR)L"ncacn_np")

enum METHODTYPE {
    method_Shutdown,
    method_Sleep,
    method_Hello,

    method_EOL  // End-Of-List
};

#define MAX_METHODNAME 16

struct METHOD {
    METHODTYPE Type;
    WCHAR Name[MAX_METHODNAME];
    int    MinParameter;
};

const METHOD Methods[]= {
    {method_Shutdown, L"SHUTDOWN", 4},
    {method_Sleep, L"SLEEP", 5},
    {method_Hello, L"HELLO", 5},

    {method_EOL, L"", 0}
};

/*
usage: pipoclient [PIPE|LPC] [endpoint] [option]
           shutdown
               shutdown RPC server
           sleep [duration]
               sleep
           hello [message]
               show message
*/

void ShowUsage() {
    wprintf(L"usage: pipoclient [PIPE|LPC] [endpoint] [method] [option]\n");
    wprintf(L"           shutdown\n");
    wprintf(L"               shutdown RPC server\n");
    wprintf(L"           sleep [duration]\n");
    wprintf(L"               sleep\n");
    wprintf(L"           hello [message]\n");
    wprintf(L"               show message\n");
}

static wchar_t upperstr[MAX_METHODNAME+1];
wchar_t *ToUpper(const wchar_t *s) {
    for ( int i=0 ; i<MAX_METHODNAME+1 ; ++i ) {
        upperstr[i]= toupper(s[i]);
        if ( s[i]==0 )
            return upperstr;
    }
    upperstr[MAX_METHODNAME]= 0;
    return upperstr;
}

int wmain(int argc, wchar_t *argv[]) {
    if ( argc<4 ) {
        ShowUsage();
        return ERROR_INVALID_PARAMETER;
    }

    LPWSTR UpperString= NULL;
    RPC_WSTR Protocol= NULL;
    UpperString= ToUpper(argv[1]);
    if ( wcscmp(UpperString, L"PIPE")==0 )
        Protocol= PROT_NP;
    else if ( wcscmp(UpperString, L"LPC")==0 )
        Protocol= PROT_LPC;
    else {
        ShowUsage();
        wprintf(L"Unknown procotol.\n");
        return ERROR_INVALID_PARAMETER;
    }

    UpperString= ToUpper(argv[3]);
    int MethodIndex= -1;
    for ( int i=0 ; Methods[i].Type!=method_EOL ; ++i ) {
        if ( wcscmp(Methods[i].Name, UpperString)==0 ) {
            MethodIndex= i;
            break;
        }
    }

    if ( MethodIndex<0 || argc<Methods[MethodIndex].MinParameter ) {
        ShowUsage();
        wprintf(L"Bad parameter.\n");
        return ERROR_INVALID_PARAMETER;
    }

    RPC_STATUS Status= RPC_S_OK;
    RPC_WSTR Binding= NULL;
 
    Status= RpcStringBindingCompose(
      NULL, Protocol, NULL, (RPC_WSTR)argv[2], NULL, &Binding);
    if (Status!=RPC_S_OK) {
        wprintf(L"RpcStringBindingCompose failed (0x%08x)\n", Status);
        return Status;
    }

    Status= RpcBindingFromStringBinding(Binding, &pipo_IfHandle);
    if (Status!=RPC_S_OK) {
        wprintf(L"RpcBindingFromStringBinding failed (0x%08x)\n", Status);
        return Status;
    }
 
     RpcTryExcept {
        wprintf(L"%s is invoked.\n", Methods[MethodIndex].Name);

        switch ( Methods[MethodIndex].Type ) {
        case method_Shutdown:
            Shutdown();
            break;
        case method_Hello:
            Hello(argv[4]);
            break;
        case method_Sleep:
            RpcSleep(_wtoi(argv[4]));
            break;
        }
    }
    RpcExcept( EXCEPTION_EXECUTE_HANDLER ) {
        printf("Caught exception: 0x%08x\n", RpcExceptionCode());
    }
    RpcEndExcept
 
    Status = RpcStringFree(&Binding);
    if (Status!=RPC_S_OK)
        wprintf(L"RpcStringFree failed (0x%08x)\n", Status);
 
    Status= RpcBindingFree(&pipo_IfHandle);
    if (Status!=RPC_S_OK)
        wprintf(L"RpcBindingFree failed (0x%08x)\n", Status);
 
    return 0;
}

void __RPC_FAR * __RPC_API midl_user_allocate(size_t len) {
    return malloc(len);
}

void __RPC_API midl_user_free(void __RPC_FAR * ptr) {
    free(ptr);
}

6. とりあえず動かす

せっかくなので動かしてみましょう。

まずは、RPC サーバーを起動します。パイプは \pipe\パイプ名 という名前じゃないと RPC_S_INVALID_ENDPOINT_FORMAT エラーになるので注意。LPC ポート名も、円記号などを含めると同じエラーが発生します。

image

実際にパイプや LPC ポートが作られたかどうかは、Sysinternals ツールで調べることができます。

Sysinternals Suite は便利なので、必ずダウンロードしておきましょう。
http://technet.microsoft.com/ja-jp/sysinternals/bb842062

名前付きパイプは、pipelist.exe で一覧を見ることができます。(結果は一部抜粋)

e:\dropbox\pipo> pipelist

PipeList v1.01
by Mark Russinovich
http://www.sysinternals.com

Pipe Name                                    Instances       Max Instances
———                                    ———       ————-
InitShutdown                                      3               -1     
lsass                                             4               -1     
protected_storage                                 3               -1     
ntsvcs                                            3               -1     
scerpc                                            3               -1     
plugplay                                          3               -1     
epmapper                                          3               -1     
LSM_API_service                                   3               -1     
ExtEventPipe_Service                              1               30     
eventlog                                          3               -1     
Winsock2\CatalogChangeListener-80-0               1                1     
atsvc                                             3               -1     
wkssvc                                            4               -1     
msmania\pipe                                      3               -1     

ちなみに同じパイプ名でサーバーを起動してもエラーになることはなく、インスタンスが 3 から 6 になりました。

LPC ポートは Winobj.exe から見ることができます。RPC Control のところにあります。

image

LPC ポートの場合は、同じポート名でサーバーの起動を試みると、RPC_S_DUPLICATE_ENDPOINT エラーが発生します。

この状態で、次に RPC クライアントを実行します。
image

何回かメソッドを呼ぶと、スレッドが切り替わる様子も確認できます。
image

RPC サーバーを起動するときに最大インスタンス数を 5 に設定しましたので、5 セッションを枯渇させると RPC_S_SERVER_TOO_BUSY (0x000006bb) エラーが発生します。

image

他にも、RPC エラーを発生させるパターンはいろいろ考えられるので、簡単に確認できるツールがあると便利です。

遊び終わったら、Shutdown メソッドでも呼んでおきます。

image