SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

CPUIDチュートリアル

ハードディスク情報の抽出 1:ハードディスクの種類の判別

Setup API 関数とWMIを用いたハードディスク情報の抽出


  • X ポスト
  • このエントリーをはてなブックマークに追加

SetupAPI関数による抽出方法

 Setup API(SetupDi*)関数を使用することでWMIとほぼ同じ情報を取得できます。

 以下の手順で取得します。

  1. SetupDiGetClassDevsGUID_DEVCLASS_DISKDRIVEを指定。
  2. SetupDiEnumDeviceInfoで各デバイス情報を抽出。
    抽出できなければ4.へ。
  3. SetupDiGetDeviceRegistryPropertyでデバイス固有情報の抽出。
    抽出後、2.へ戻る。
  4. SetupDiDestroyDeviceInfoListSetupDiGetClassDevsで開いたHDEVINFOをclose。

 SetupDiGetDeviceRegistryProperty関数の引数で注目したいのは、以下の2つのパラメータです。

  • SPDRP_ENUMRATOR_NAME
    接続しているインターフェイスの型(例:「IDE」「SCSI」「USB」「SDP2(IEEE1394)」など)
  • SPDRP_FRIENDLYNAME
    ハードディスクの型番
Setup API関数による実装例
#include <stdio.h>
#include <windows.h>
#include <setupapi.h>
#include <devguid.h>

PWCHAR
GetDeviceRegistryProperty(
    IN HDEVINFO            hDI,
    IN PSP_DEVINFO_DATA    pdevinfo,
    IN DWORD               Property
    );

int
main(void)
{
    HDEVINFO hDI = INVALID_HANDLE_VALUE;
    SP_DEVINFO_DATA devinfo;
    int i = 0;
    PWCHAR pw = NULL;

    // create HDEVINFO.
    hDI = SetupDiGetClassDevs(
            (LPGUID) &GUID_DEVCLASS_DISKDRIVE,
            NULL, // Enumerator
            0,
            DIGCF_PRESENT | DIGCF_ALLCLASSES );

    if( INVALID_HANDLE_VALUE == hDI ){ return -1; }

    // enum all GUID_DEVCLASS_DISKDRIVE
    for(i=0;;i++){
        ZeroMemory(&devinfo, sizeof(devinfo));
        devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
        if(!SetupDiEnumDeviceInfo(hDI, i, &devinfo)){
            break;
        }
        // SPDRP_ENUMRATOR_NAME
        pw = GetDeviceRegistryProperty(
            hDI, &devinfo, SPDRP_ENUMRATOR_NAME);
        if( NULL!=pw ){
            printf("[%ws] ", pw);
            free(pw);
        }
        // SPDRP_FRIENDLYNAME
        pw = GetDeviceRegistryProperty(
            hDI, &devinfo, SPDRP_FRIENDLYNAME);
        if( NULL!=pw ){
            printf("%ws", pw);
            free(pw);
        }
        printf("\n");
    }

    //  Cleanup
    SetupDiDestroyDeviceInfoList(hDI);
    return 0;
}

PWCHAR
GetDeviceRegistryProperty(
    IN HDEVINFO            hDI,
    IN PSP_DEVINFO_DATA    pdevinfo,
    IN DWORD            Property
    )
{
    DWORD dwType;
    DWORD dwLen = 0;
    DWORD dwErr;
    PWCHAR pw = NULL;

    if( !SetupDiGetDeviceRegistryPropertyW(
        hDI,pdevinfo,Property,&dwType,NULL,0,&dwLen) ){
        dwErr = GetLastError();
        if( ERROR_INSUFFICIENT_BUFFER != dwErr ){
            return NULL;
        }
    }
    if( 0 == dwLen ){ return NULL; }
    pw = (PWCHAR)malloc(dwLen);
    if( NULL != pw ){
        if( !SetupDiGetDeviceRegistryPropertyW(
            hDI,pdevinfo,Property,&dwType,pw,dwLen,&dwLen) ){
            dwErr = GetLastError();
            free(pw);
            return NULL;
        }
    }

    return pw;
}

 上記のパラメータ群は実際には「HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum」下に保存されているregistry情報を抽出しています。DDKによるとEnum以下のregistry情報を直接読み出すことは、デバッグ目的以外では推奨されていません。当然、書き込むことや消すことも禁止されています。まっとうな方法を用いるなら、Setup API関数を介してEnum下のregistry情報を取得することをお勧めします。

 GUID_DEVCLASS_DISKDRIVE以外にもさまざまなClassが用意されています。「devguid.h」を参照してみてください。これらのClass情報を出力するツール「GUID Explorer」というソフトウェアを使用することで、Setup APIを用いて取得できる情報の一覧を眺めることが容易になります。

SetupDiGetDeviceRegistryProperty()とSetupDiGetDeviceRegistryPropertyW()の違いについて
 SetupDiGetDeviceRegistryProperty()として紹介されているAPIですが、今回は明示的にSetupDiGetDeviceRegistryPropertyW()を使用しています。
 Windowsでは、文字列を扱うWindows APIであるfoo()は、ASCII(DBCS/MBCS)用の「fooA()」とUnicode用の「fooW()」の2通り用意されているのが一般的です。foo()は、コンパイル時に「UNICODE」が設定されていれば「fooW()」、そうでなければ「fooA()」を使用する仕組みになっています。
DEFINEによる切り分け
#ifdef UNICODE
// UNICODE が設定されている場合、fooは fooW を使用します
#define foo fooW
#else
// UNICODE が設定されていない場合、fooは fooA を使用します
#define foo fooA
#endif
 今回、DBCS環境での構築を行いましたので明示的に指定しないとfoo()fooA()になってしまいます。なぜ明示的にUnicodeを指定したかと言うと、SetupDiGetDeviceRegistryProperty()関数特有のバグに起因します。今回のソースをSetupDiGetDeviceRegistryPropertyWSetupDiGetDeviceRegistryPropertyAに変更してみれば分かるのですが、多分真っ正直に変更すると2度目の呼び出しでもERROR_INSUFFICIENT_BUFFERとエラーが返却されるはずです。
 このバグを修正するには1度目に取得するバッファ数を「2倍」にしてメモリ取得することで解決します(私はこの作業を2倍返しと言っています)。これらのバグは、内部のUnicode⇔ASCIIの変換で演算ミスから生じているのではないかと考えています。
 例えば文字列barを返却したい場合、以下の演算ミスが考えられます。返却する値barはASCIIで「Aあ」(半角小文字の「A」とひらがなの「あ」の組み合わせ)という文字が入っていることとします。
  1. 1回目の問い合わせ(バッファ数の問い合わせ)でlstrlenA(bar)+1を返却 → 3+1byte必要と返却
  2. 2回目の問い合わせ(指定されたバッファ数による問い合わせ)で、関数内部の何かのタイミングでbarをASCII→Unicode→ASCIIと処理する場合が生じる
  3. Unicodeに変換するには(2+1)*2byte必要
    よってバッファ不足として返却せざるを得ない
 SetupDiGetDeviceRegistryPropertyW()に限らず、特にWindows 9x系とWindows NT系の互換を必要とするAPIにこのようなバグが多いようです。実際MSKB:259695でそのようなバグを匂わせるコメントがありました。
 ちなみにMSKB:259695の修正方法では動きません。以下の行を変更してください。これで動作するはずです。
修正前
buffer = LocalAlloc(LPTR,buffersize * 2);
修正後
buffersize*=2; buffer = LocalAlloc(LPTR,buffersize);
 上記のような文字列で問題になりそうなAPIを使用する場合、私は明示的にUnicodeを使うようにしています。ただしこの場合、文字列処理が面倒なことになります。
 

まとめ

 今回はSetup API関数、WMIを用いてハードディスクの種類を抽出することができました。

 では、OSはこれらの情報をどのように取得しているのでしょうか。次回は、IDEのハードディスク情報である「Identify」コマンドを使用し、ハードディスクの生の情報を抜き出すサンプルプログラムを作成する予定です。

 今後、「ハードディスクに関する考察シリーズ」の構成として、次のように進めていこうと考えています。

  • Setup API関数を組み合わせたIDE認識情報(Identify)の抽出プログラム(smart api)
  • IDEのSMART情報の抽出プログラム(smart api)
  • SCSI/USB/IEEE1394の認識情報の抽出プログラム(spti)

 最終的にはデモプログラム「littleDISK」を作成し、回を増すごとに機能アップしていく予定です。

参考資料

WMI

SetupAPI関数

  • MSKB:311272 デバイスマネージャとして機能するDevConコマンドラインユーティリティ
    DevConはSetup API関数を駆使したユーティリティです。DDKにはサンプルソースも用意されています。
  • MSKB:264203 [Enumdisk1.exe]ディスクデバイスを列挙するためのEnumdiskサンプル
    GUID_DEVCLASS_DISKDRIVEの使用例。
  • MSKB:259695 SetupDiを使用してハードウェア機器を列挙する方法
    SetupAPI関数を使用例。
  • Enumerate Installed Devices Using Setup API
    The Code Projectのサンプル。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
CPUIDチュートリアル連載記事一覧

もっと読む

この記事の著者

Mc.N(エムシイエヌ)

SyncHack 管理人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/351 2008/08/21 21:10

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング