Jaa


SetupDi API と DevCon ~ SetupDi API の使い方編 ~

まさかたです。こんにちは。

 

前回、「DevCon と SetupDi API ~ DevCon の使い方編 ~」で、DevCon とその使い方について、簡単にご紹介させていただきました。

今回は、サンプルソースコードでどういう風に SetupDi API が使われているのかを見て、その基本的な使い方を紹介したいと思います。

 

■DevCon のコマンドオプションとコールバック関数

さて、DevCon コマンドに渡すオプションが、それぞれどのコードに対応するかですが、これは、Cmds.cpp 内に、各コマンドに対応してコールされる関数名が、DispatchTable として定義されておりますので、ここから、オプション毎にどの関数が呼ばれているかが簡単に分かります(cmds.cpp 2308 行目より)。

(結局、オプション名の直前に “cmd” がついた関数を見ればいいわけです。)

2308 DispatchEntry DispatchTable[] = {

2309 { TEXT("classfilter"), cmdClassFilter, MSG_CLASSFILTER_SHORT, MSG_CLASSFILTER_LONG },

2310 { TEXT("classes"), cmdClasses, MSG_CLASSES_SHORT, MSG_CLASSES_LONG },

2311 { TEXT("disable"), cmdDisable, MSG_DISABLE_SHORT, MSG_DISABLE_LONG },

2312 { TEXT("driverfiles"), cmdDriverFiles, MSG_DRIVERFILES_SHORT, MSG_DRIVERFILES_LONG },

2313 { TEXT("drivernodes"), cmdDriverNodes, MSG_DRIVERNODES_SHORT, MSG_DRIVERNODES_LONG },

2314 { TEXT("enable"), cmdEnable, MSG_ENABLE_SHORT, MSG_ENABLE_LONG },

2315 { TEXT("find"), cmdFind, MSG_FIND_SHORT, MSG_FIND_LONG },

2316 { TEXT("findall"), cmdFindAll, MSG_FINDALL_SHORT, MSG_FINDALL_LONG },

2317 { TEXT("help"), cmdHelp, MSG_HELP_SHORT, 0 },

2318 { TEXT("hwids"), cmdHwIds, MSG_HWIDS_SHORT, MSG_HWIDS_LONG },

2319 { TEXT("install"), cmdInstall, MSG_INSTALL_SHORT, MSG_INSTALL_LONG },

2320 { TEXT("listclass"), cmdListClass, MSG_LISTCLASS_SHORT, MSG_LISTCLASS_LONG },

2321 { TEXT("reboot"), cmdReboot, MSG_REBOOT_SHORT, MSG_REBOOT_LONG },

2322 { TEXT("remove"), cmdRemove, MSG_REMOVE_SHORT, MSG_REMOVE_LONG },

2323 { TEXT("rescan"), cmdRescan, MSG_RESCAN_SHORT, MSG_RESCAN_LONG },

2324 { TEXT("resources"), cmdResources, MSG_RESOURCES_SHORT, MSG_RESOURCES_LONG },

2325 { TEXT("restart"), cmdRestart, MSG_RESTART_SHORT, MSG_RESTART_LONG },

2326 { TEXT("sethwid"), cmdSetHwid, MSG_SETHWID_SHORT, MSG_SETHWID_LONG },

2327 { TEXT("stack"), cmdStack, MSG_STACK_SHORT, MSG_STACK_LONG },

2328 { TEXT("status"), cmdStatus, MSG_STATUS_SHORT, MSG_STATUS_LONG },

2329 { TEXT("update"), cmdUpdate, MSG_UPDATE_SHORT, MSG_UPDATE_LONG },

2330 { TEXT("updateni"), cmdUpdateNI, MSG_UPDATENI_SHORT, MSG_UPDATENI_LONG },

2331 { TEXT("dp_add"), cmdDPAdd, MSG_DPADD_SHORT, MSG_DPADD_LONG },

2332 { TEXT("dp_delete"), cmdDPDelete, MSG_DPDELETE_SHORT, MSG_DPDELETE_LONG },

2333 { TEXT("dp_enum"), cmdDPEnumLegacy,MSG_DPENUM_SHORT, MSG_DPENUM_LONG },

2334 { TEXT("?"), cmdHelp, 0, 0 },

2335 { NULL,NULL }

2336 };

 

試しに、find オプションの場合として、cmdFind() の中をのぞいてみると、中でさらに EnumerateDevices() という関数がコールされています。

failcode = EnumerateDevices(BaseName,Machine,DIGCF_PRESENT,argc,argv,FindCallback,&context);

この関数は、DevCon.cpp の 798 行目から定義されており、以下に示すように、その他のオプションの処理でも呼ばれている関数です。

ですので、今回は、SetupDi API を使ったデバイスの情報を採取する共通の手段として、主にこの中で行われている処理について見ていきたいと思います。

ü HwIDs

ü DriverFiles

ü DriverNodes

ü Resources

ü Stack

ü Status

ü Find

ü FindAll

ü Enable

ü Disable

ü Remove

ü Restart

ü SetHwID

 

n Setup API の使い方

ここでは、EnumerateDevices() のソースコードを細かく 1行 ずつ見ていくのではなく、SetupDi API を使っている箇所にポイントを絞ります。

すると、おおまかな処理の流れは以下のようになると思います。

1. SetupDiGetClassDevsEx() で、デバイス情報セットへのハンドル(HDEVINFO)を取得

2. SetupDiEnumDeviceInfo() で、デバイスを列挙

3. SetupDiGetDeviceRegistryProperty() で、HwID などのデバイスの詳細情報を取得

4. 3 で取得した情報から目的のデバイスであるかを判別し、オプション毎に定義された Callback 関数を呼んで独自の処理を実行

5. 最後は、SetupDiDestroyDeviceInfoList() で、使い終わったデバイス情報セットを破棄

以下では、それぞれの API について、その定義と使われ方を見ながら、デバイスを列挙する仕組みや概念についてもご紹介したいと思います。

ただし、SetupDiDestroyDeviceInfoList() については、ハンドルを破棄するだけのものですので、詳しい説明は省略させていただきます。

 

1. SetupDiGetClassDevsEx

2. SetupDiEnumDeviceInfo

3. SetupDiGetDeviceRegistryProperty

 

1. SetupDiGetClassDevsEx

この API は何をするかと言うと、デバイスを列挙する前に、列挙する元となる様々なデバイスの情報をかき集めて、ひとつの「デバイス情報セット」と呼ばれる、デバイスの情報の集合体のようなものを構築して、それにアクセスするためのハンドルを返してくれます。

この「デバイス情報セット」の詳細については、以下の 【補足】デバイス情報セットについて にて補足の説明をしていますので、併せて見ていただければと思います。

 さて、この関数を使ってデバイス情報セットを構築する時、どんな種類のデバイスの情報をかき集めるのかを、引数を使って指定することができます。

API そのものの定義は以下の通りです。

HDEVINFO  SetupDiGetClassDevsEx( IN LPGUID ClassGuid , OPTIONAL IN PCTSTR Enumerator , OPTIONAL IN HWND hwndParent , OPTIONAL IN DWORD Flags , IN HDEVINFO DeviceInfoSet , OPTIONAL IN PCTSTR MachineName , OPTIONAL IN PVOID Reserved );

 

以下では、SetupDiGetClassDevsEx() でポイントとなる、ClassGUID、Enumerator、Flags のパラメータについて見ていきます。

Ø ClassGUID

ClassGUID には、情報を集めてくるデバイスの種類を、Device Setup Class または Device Interface Class という Class で指定することができます。

Class GUID は、その名の通り、上記の Class を表す GUID になります。

上記の2つの Class の詳細については、さらに以下の 【補足】Device Setup Class と Device Interface Class について でも、さらに説明をしていますので、そちらもご参照ください。

ここで指定したクラスに属するデバイスだけが、デバイス情報セットに含まれることになります。

Ø Enumerator

この引数は、デバイス情報セットに含めるデバイスの種類をフィルタリングするために指定するものです。

このパラメータの意味合いは、以下で説明する Flags で何を指定するかによっても変わります。

Flags で、DIGCF_DEVICEINTERFACE を指定しない場合 は、Plug and Play の列挙子(Enumerator)の ID として認識され、例えば、”PCI”、”USB”、"PCMCIA"、”SCSI” 等が指定できます。

ここで、他に指定できる Enumerator としては、具体的には、以下のレジストリキー直下のキー名が指定できます。

 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum

 

 EnumRegistryKey

 

また、DIGCF_DEVICEINTERFACE を指定した場合は、 PnP のデバイスインスタンス IDとして認識されます。

Device Instance IDs

https://msdn.microsoft.com/en-us/library/dd567984.aspx

Ø Flags

Flags も、さらにデバイス情報セットに含めるデバイスの種類をフィルタリングするために指定するもので、指定できる Flag と、それぞれの意味は以下のようになります。

この Flag を組み合わせて、さらにデバイスの絞込みをすることができます。

Flags

対象となるデバイス

DIGCF_ALLCLASSES

Device Setup Class および Device Interface Class を含む全ての Class のデバイス

DIGCF_DEVICEINTERFACE

全ての Device Interface Class のデバイス

DIGCF_DEFAULT

システムにデフォルトで組み込まれている Device Interface Class のデバイス

(後から追加した Class は無視)

DIGCF_PRESENT

現在、システムに接続されているデバイスのみ

(Non Present Device や Phantom Device は無視)

DIGCF_PROFILE

現在のハードウェア プロファイルに含まれるデバイスのみ

 

以上を踏まえて、DevCon で使われているコードを見てみます。(devcon.cpp 898 行目より)

898 devs = SetupDiGetClassDevsEx(numClass ? &cls : NULL,

899 NULL,

900 NULL,

901 (numClass ? 0 : DIGCF_ALLCLASSES) | Flags,

902 NULL,

903 Machine,

904 NULL);

 

上記の numClass は、コマンドのオプションで、Class 名がいくつ指定されたかを表すものです。

これが 0 の場合は、Class が指定されていないということになりますので、第1引数の ClassGUID には、NULL が指定されます。

逆に、クラス名を指定した場合には、NULL ではなく、cls が入力引数となります。

この cls には、上記のコードの前でコールしている SetupDiClassGuidsFromNameEx() から取得した、ClassGUID が入っていて、これを第1引数に指定して Class を指定しています。

なお、該当するコードの抜粋は以下の通りです。(devcon.cpp 854 行目より)

854 if(argc>skip && argv[skip][0]==CLASS_PREFIX_CHAR && argv[skip][1]) {

855 if(!SetupDiClassGuidsFromNameEx(argv[skip]+1,&cls,1,&numClass,Machine,NULL)

SetupDiClassGuidsFromNameEx() を使えば、DevCon のコマンドオプションで指定したクラス名から ClassGUID を取得することができます。

また、API の定義は以下の通りです。

WINSETUPAPI BOOL WINAPI SetupDiClassGuidsFromNameEx( IN PCTSTR ClassName , OUT LPGUID ClassGuidList , IN DWORD ClassGuidListSize , OUT PDWORD RequiredSize , IN PCTSTR MachineName , OPTIONAL IN PVOID Reserved );  

 

さらに、DevCon 内のソースコードでは、Flags には、EnumerateDevices() に与えられたFlags 引数と、0 または DIGCF_ALLCLASSES で OR を取っていますので、基本的には EnumerateDevices() に与えられた Flags が有効になります。

具体的に Flags に与えられている引数の内容を見てみると、多くの場合、DIGCF_PRESENT が指定されており、FindAll オプションの場合のみ、0 が指定されています。

このことから、FindAll 以外では、システムに接続されているデバイスのみが検索対象となり、FindAll の場合には、システムに接続されていないデバイスも検索対象となっているということが分かります。

つまり、他のオプションでも、この Flags で、DIGCF_PRESENT を指定しなければ、システムに接続されていないデバイス(例えば、過去に接続したけど、今は接続していない USB デバイスなど)も、操作の対象とすることができるわけです。

これを応用すれば、例えば、Remove オプションで、DIGCF_PRESENT の Flag を外せば、システムに接続されていないデバイスを削除の対象とすることができます。 

【補足】デバイス情報セットについて

デバイス情報セットとは、SetupDiGetClassDevs() によって指定された Class に含まれる、複数のデバイスの情報の集合体のようなものです。

下の図は、デバイス情報セットに含まれる、デバイスの情報を、デバイス情報エレメントや、DevNode、Device Interaface という概念で表した図で、こちらの MSDN のページでも参照することができます。

デバイス情報エレメントは、個々のデバイスの情報を表すもので、その中には、1つの Devnode と、1つ以上の Device Interface が含まれています。

DevNode は、個々のデバイス インスタンスに対応するものです。

また、Device Interface については、さらに以下の 【補足】Device Setup Class と Device Interface Class について でも説明をしていますので、そちらも見ていただければと思います。

DeviceInfoSet

【補足】Device Setup Class と Device Interface Class について

Device Setup ClassとDevice Interface Class というのは、デバイスをある基準で分類した時の種類を表すもので、それぞれに分類する時の基準が微妙に違います。

ü Device Setup Class

Device Setup Class とは、そのデバイスをインストールする時のセットアップ方法やデバイスの設定の仕方が同じものを、Class として分類したものです。

デバイスのインストールで出てくる Class Installer や Co-installer というのは、そのデバイスの Setup Class に応じて用意されるもので、Class 毎に必要になる特別なインストールの方法や設定を行うためのものということになります。

例えば、システムで定義されている Class として、Ports Class がありますが、この Class Installer は、デバイスに対して、COM port 名を割り当てる作業をしたりします。

また、この Setup Class の識別は、Class そのものの名前ではなく、実際には、それぞれに割り当てられた独自の GUID を使って行われます。

システム上に、どんな Device Setup Class が存在し、GUID として何が割り当てられているかは、以下のレジストリキーを参照することで確認することも可能です。

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Class\ <ClassGUID>

ただし、このレジストリキーは、デバッグ目的などで参照することのみ可能なもので、ドライバやユーザーモードアプリケーションから直接アクセスして、編集したりしてはいけないものですので、注意してください。

また、既に System で定義された GUID について詳しいことは、以下を見ていただきたいと思います。

System-Supplied Device Setup Classes

https://msdn.microsoft.com/en-us/library/ms791134.aspx

ü Device Interface Class

Device Setup Class に対して、Device Interface Class とは、その名の通り、そのデバイスが、他のアプリケーションやドライバに対して持つ Interfaceという側面から、Class として分類したものです。

デバイスのソフトウェア的な Interface は、そのデバイスを制御するドライバに当たりますから、Device Interface Class とは、そのデバイスを制御するドライバの種類ということになります。

ドライバは、必ず自分がどの Device Interface Class に属するものであるのかを、ドライバの AddDevice 関数の中で IoRegisterDeviceInterface API を使って登録しなくてはなりません。

この Class も、Device Setup Class と同様に、GUID を使って表されます。

既に System で定義済みの Device Interface Class の詳細については、以下を見ていただければと思います。

System-Defined Device Interface Classes

https://msdn.microsoft.com/en-us/library/bb663138.aspx

1. 2. SetupDiEnumDeviceInfo

SetupDiEnumDeviceInfo 関数では、デバイス情報セットの中に含まれる一つ一つのデバイス情報エレメントを列挙していきます。

API 自体の定義は以下の通りです。

WINSETUPAPI BOOL WINAPI SetupDiEnumDeviceInfo( IN HDEVINFO DeviceInfoSet , IN DWORD MemberIndex , OUT PSP_DEVINFO_DATA DeviceInfoData );

そして、DevCon で使われているコードは以下の通りです。(devcon.cpp 943行目)

for(devIndex=0;SetupDiEnumDeviceInfo(devs,devIndex,&devInfo);devIndex++) {

ここで、SetupDiGetClassDevs() で取得したデバイス情報セット devs を第1引数に渡し、第2引数の devIndex をインクリメントしながら、繰り返し同 API をコールすることによって、次々にデバイスの情報を devInfo で受け取っています。

列挙されたデバイス情報エレメントの情報は、SP_DEVINFO_DATA 構造体で表されます。

3. SetupDiGetDeviceRegistryProperty

SetupDiEnumDeviceInfo() で取得した SP_DEVINFO_DATA 構造体を使って、SetupDiGetDeviceRegistryProperty() をコールし、さらにデバイスに関する詳細な情報を取得することが可能になります。

また、SP_DEVINFO_DATA 構造体を使って、デバイスの情報を取得できる SetupDi API は、SetupDiGetDeviceRegistryProperty() 以外にもさまざまあります。

さて、SetupDiGetDeviceRegistryProperty() の定義は以下の通りです。

WINSETUPAPI BOOL WINAPI SetupDiGetDeviceRegistryProperty( IN HDEVINFO DeviceInfoSet , IN PSP_DEVINFO_DATA DeviceInfoData , IN DWORD Property , OUT PDWORD PropertyRegDataType , OPTIONAL OUT PBYTE PropertyBuffer , IN DWORD PropertyBufferSize , OUT PDWORD RequiredSize OPTIONAL );

また、DevCon で使われているコードは以下の通りです。(devcon.cpp の 517 行目より)

while(!SetupDiGetDeviceRegistryProperty(Devs,DevInfo,Prop,&dataType,(LPBYTE)buffer,size,&reqSize)) {

ここで、第1引数 DeviceInfoSet には、デバイス情報セットを、第2引数 DeviceInfoData には、先に取得した SP_DEVINFO_DATA 構造体を指定します。

第3引数 Property には、どのプロパティを取得してくるのか、その種類を指定し、それ以降の引数では、プロパティの値を取得するためのレジストリの値のタイプ(REG_DWORD とか REG_SZなど)や、バッファのポインタとバッファサイズなどを指定します。

また、この API の名前からも察しが付くかと思いますが、これらの情報はレジストリから取得しています。

また、その情報源は、以下のレジストリキー配下に格納されている値になります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum

それじゃ、こんな面倒くさいことをせずに、ここに直接アクセスして情報を取ってくればいいんじゃない、などと思われるかもしれませんし、実際、こちらに直接アクセスして、手を加えているのですが…というお問い合わせをいただくこともあります。

しかし、このレジストリキーは、デバッグ目的などでレジストリエディタなので参照するだけならよいのですが、以下の技術情報にもありますように、OS でのみ管理することを前提としています。

HKLM\SYSTEM\CurrentControlSet\Enum Registry Tree

https://msdn.microsoft.com/en-us/library/dd568017.aspx

そのため、ドライバやアプリケーションから直接アクセスして操作すると、システム全体として不整合が発生する恐れもあるため、そのようなことはしてはいけないレジストリなのです。

というわけで、カーネルモードドライバでは、IoGetDeviceProperty()、ユーザーモードなら SetupDiGetDeviceRegistryProperty() というように、間接的にアクセスするための API が用意されていますので、情報の取得を行う必要がある場合にはこちらの API をお使いいただく必要があります。

【補足】Device Interface の列挙について

先の「 【補足】デバイス情報セットについて」でご紹介した図には、各デバイス情報エレメントに、さらに「Device Interface」 が含まれる形となっていますが、この「Device Interface」を列挙するには、SetupdiEnumDeviceInterfaces() を使います。

SetupdiEnumDeviceInterfaces() の定義は以下の通りです。

WINSETUPAPI BOOL WINAPI SetupDiEnumDeviceInterfaces( IN HDEVINFO DeviceInfoSet , IN PSP_DEVINFO_DATA DeviceInfoData , OPTIONAL IN LPGUID InterfaceClassGuid , IN DWORD MemberIndex , OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData );

ここで、第2引数に、 DeviceInfoData を指定することができますが、これによって、列挙する範囲を特定のデバイス情報エレメントに絞ることができるわけです。

逆に指定しなければ、SetupDiGetClassDevs() で取得したデバイス情報セット全体に含まれる Device Interface が列挙されることになります。

そして、列挙した Device Interface に対して、詳細な情報を取得するには、SetupDiEnumDeviceInterfaces() で取得した SP_DEVICE_INTERFACE_DATA 構造体を、SetupdiGetDeviceInterfaceDetail() に渡してコールすることで、SP_DEVICE_INTERFACE_DETAIL_DATA 構造体を取得します。

この構造体から、デバイスハンドルをオープンするために CreateFile() で指定する、Symbolic Link 名(DevicePath[])を知ることができます。

typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA { DWORD cbSize; TCHAR DevicePath[ANYSIZE_ARRAY];} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;

今回は、ソースコードの細かな内容は、かなりスキップしてしまいましたが、基本的な SetupDi API の使い方と、それにまつわる概念について簡単にご紹介いたしました。

SetupDi API で、デバイスの検索、情報の取得、設定の変更などを行う場合でも、基本的には呼び出しの流れはほぼ同じです。

今後、様々な SetupDi API を使うときに、少しでも皆様のお役に立てれば幸いです。

それでは。