SetupAPI関数による抽出方法
Setup API(SetupDi*)関数を使用することでWMIとほぼ同じ情報を取得できます。
以下の手順で取得します。
SetupDiGetClassDevs
でGUID_DEVCLASS_DISKDRIVE
を指定。SetupDiEnumDeviceInfo
で各デバイス情報を抽出。抽出できなければ4.へ。SetupDiGetDeviceRegistryProperty
でデバイス固有情報の抽出。抽出後、2.へ戻る。SetupDiDestroyDeviceInfoList
でSetupDiGetClassDevs
で開いたHDEVINFO
をclose。
SetupDiGetDeviceRegistryProperty
関数の引数で注目したいのは、以下の2つのパラメータです。
- SPDRP_ENUMRATOR_NAME
接続しているインターフェイスの型(例:「IDE」「SCSI」「USB」「SDP2(IEEE1394)」など)
- SPDRP_FRIENDLYNAME
ハードディスクの型番
#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()
として紹介されているAPIですが、今回は明示的にSetupDiGetDeviceRegistryPropertyW()
を使用しています。Windowsでは、文字列を扱うWindows APIである
foo()
は、ASCII(DBCS/MBCS)用の「fooA()
」とUnicode用の「fooW()
」の2通り用意されているのが一般的です。foo()
は、コンパイル時に「UNICODE」が設定されていれば「fooW()
」、そうでなければ「fooA()
」を使用する仕組みになっています。#ifdef UNICODE // UNICODE が設定されている場合、fooは fooW を使用します #define foo fooW #else // UNICODE が設定されていない場合、fooは fooA を使用します #define foo fooA #endif
foo()
はfooA()
になってしまいます。なぜ明示的にUnicodeを指定したかと言うと、SetupDiGetDeviceRegistryProperty()
関数特有のバグに起因します。今回のソースをSetupDiGetDeviceRegistryPropertyW
→SetupDiGetDeviceRegistryPropertyA
に変更してみれば分かるのですが、多分真っ正直に変更すると2度目の呼び出しでもERROR_INSUFFICIENT_BUFFER
とエラーが返却されるはずです。このバグを修正するには1度目に取得するバッファ数を「2倍」にしてメモリ取得することで解決します(私はこの作業を2倍返しと言っています)。これらのバグは、内部のUnicode⇔ASCIIの変換で演算ミスから生じているのではないかと考えています。
例えば文字列barを返却したい場合、以下の演算ミスが考えられます。返却する値barはASCIIで「Aあ」(半角小文字の「A」とひらがなの「あ」の組み合わせ)という文字が入っていることとします。
- 1回目の問い合わせ(バッファ数の問い合わせ)で
lstrlenA(bar)+1
を返却 → 3+1byte必要と返却 - 2回目の問い合わせ(指定されたバッファ数による問い合わせ)で、関数内部の何かのタイミングでbarをASCII→Unicode→ASCIIと処理する場合が生じる
- 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);
まとめ
今回はSetup API関数、WMIを用いてハードディスクの種類を抽出することができました。
では、OSはこれらの情報をどのように取得しているのでしょうか。次回は、IDEのハードディスク情報である「Identify」コマンドを使用し、ハードディスクの生の情報を抜き出すサンプルプログラムを作成する予定です。
今後、「ハードディスクに関する考察シリーズ」の構成として、次のように進めていこうと考えています。
- Setup API関数を組み合わせたIDE認識情報(Identify)の抽出プログラム(smart api)
- IDEのSMART情報の抽出プログラム(smart api)
- SCSI/USB/IEEE1394の認識情報の抽出プログラム(spti)
最終的にはデモプログラム「littleDISK」を作成し、回を増すごとに機能アップしていく予定です。
参考資料
WMI
- Scriptomaticツール
WMICode Creatorと同じくWMI自動生成ツール。
- 「論理ドライブと物理ディスクを相互に関連付ける方法はありますか」
Hey, Scripting Guy!のWMI使用例。
- WMI(Windows Management Instrumentation)
宇宙仮面さんのC#によるWMI使用例。
- Wmi Program
SASAさんのC++によるWMI使用例。面倒である理由が分かってもらえるはず。
- WMI Fun !!
とおさんのWMIサンプル集。
SetupAPI関数
- MSKB:311272 デバイスマネージャとして機能するDevConコマンドラインユーティリティ
DevConはSetup API関数を駆使したユーティリティです。DDKにはサンプルソースも用意されています。
- MSKB:264203 [Enumdisk1.exe]ディスクデバイスを列挙するためのEnumdiskサンプル
GUID_DEVCLASS_DISKDRIVEの使用例。
- MSKB:259695 SetupDiを使用してハードウェア機器を列挙する方法
Setup
API
関数を使用例。 - Enumerate Installed Devices Using Setup API
The Code Projectのサンプル。