WindowsでのUSB機器接続方式の知識マップ(知識グラフ)
この記事は、WindowsアプリからUSB機器を扱う4つの接続方式(仮想COMポート・HID・WinUSB・ベンダー提供SDK)を、機器がデバイスマネージャーでどのUSBデバイスクラスとして見えるかを起点に選ぶ考え方を扱う。標準クラスドライバーで足りなければWinUSB、複数アプリの同時アクセスが必要ならUMDFドライバー、それでも無理ならKMDFドライバーという順で検討するのが公式の選定順序であり、複合デバイスやHIDの排他コレクションなど機器側の制約が識別・実装方法を左右する。どの方式でも機器の一意識別とPnP通知による抜き差し追従を自分で設計する必要があり、機器切断によるハンドル無効化への対処が欠かせない。ドライバーを配布する場合は、自作の.sysがなくてもカタログ署名が必要で、カーネルモードドライバーを含むならさらにMicrosoftによる署名とEVコード署名証明書が要る。
USB接続方式の選定
機器がデバイスマネージャーで何として見えるかを起点に、標準クラスドライバー・WinUSB・UMDF・KMDFの順で検討する、USB機器とWindowsアプリを接続する方式の選び方。
USB機器アクセス方式の選び方
USBデバイスクラス
USB-IFが定めるクラスコードによる機器の分類で、該当するクラスに応じてWindowsが標準クラスドライバーを自動的にロードするかどうかが決まる。
クラスコード
WinUSB方式
Microsoft提供の汎用USBドライバーWinusb.sysを機能ドライバーとして載せ、ユーザーモードから直接エンドポイントに読み書きできるようにするUSB機器接続方式。
UMDFドライバー
複数のアプリからの同時アクセスに対応できる、ユーザーモードで動作するWindowsドライバーフレームワーク。
KMDFドライバー
UMDFでも対応できない要件(フィルタードライバーの積み上げなど)のときに検討する、カーネルモードで動作するWindowsドライバーフレームワーク。
複合デバイス
1本のUSBケーブルの先に複数の機能を持ち、Usbccgp.sysによって機能ごとに別々のデバイスとして展開されるUSB機器。
仮想COMポート方式
USBのCDC(ACM)クラスを名乗る機器にUsbser.sysなどが自動的に載り、アプリからはただのCOMポートとして扱えるようになるUSB機器接続方式。
USBシリアル
HID方式
ドライバーの配布なしにWindows標準のHidclass.sys/Hidusb.sysで双方向のレポートをやり取りできる、USB機器接続方式の1つ。
Microsoft OS記述子(WinUSBの互換ID)
機器のファームウェアが互換IDとしてWINUSBを報告するために実装する、Microsoft独自のUSB記述子(バージョン1.0と2.0がある)。
複数アプリからの同時アクセス
複数のアプリケーションが同時に同じUSB機器へアクセスするという要件。
機器の一意識別
COM番号やデバイスマネージャー上の並び順ではなく、VID/PID・シリアル番号・デバイスインターフェースパスなどで個々のUSB機器を識別する実装。
VID/PID+シリアル番号での識別
排他HIDコレクション
マウス・キーボード・タッチスクリーン・ペンなど、WindowsのRaw Input Managerが排他で開くため、他のアプリからは読み書きできないHIDのトップレベルコレクション。
Overlapped I/O
FILE_FLAG_OVERLAPPEDを付けて開いたハンドルに対し、操作ごとのOVERLAPPED構造体を渡して行うWin32の非同期I/Oの具体的な実装形態。モードはハンドル単位でCreateFileの時点に決まる。
オーバーラップドI/O
非同期I/O(Win32)
GetOverlappedResult
非同期操作の結果(成否と転送バイト数)を、対応するOVERLAPPED構造体から取得するWin32 API。bWait=TRUEで完了まで待つこともできる。
CancelIoEx
どのスレッドが発行したかに関係なく、指定ハンドルの未完了I/Oにキャンセルを要求できるWin32 API。取り消された操作はERROR_OPERATION_ABORTEDでの完了として返る。
USB機器のPnP通知
CM_Register_NotificationやRegisterDeviceNotificationを使って、USB機器の到着・削除をアプリがポーリングせずに検知する仕組み。
機器切断によるハンドルの無効化
USB機器の取り外しによって開いていたハンドルが無効になり、そのままI/Oを続けるとアプリの異常終了を招きかねない状態。
Windows Forms
デスクトップGUIアプリケーションを構築するための.NET/.NET FrameworkのUIフレームワーク。
USBセレクティブサスペンド
アイドル状態のUSB機器を低消費電力状態に入れるWindowsの電源管理機能。復帰に時間がかかるため初回応答の遅延の原因になり得る。
ドライバーパッケージのカタログ署名
PnPのデバイスインストールでドライバーパッケージがDriver Storeにステージされるために必要な、カタログファイル(.cat)へのWHQLまたはSPC署名の要件。
.catファイルの署名
カーネルモードドライバー署名(Windows 10 1607以降)
Windows 10 バージョン1607以降、新規のカーネルモードドライバーがロードされるためにDev Portal(Partner Center)経由でMicrosoftによる署名を受けることを求める要件。
Dev Portal署名
EVコード署名証明書
発行元のより厳格な本人確認を経て発行されるコード署名証明書。現在はSmartScreenの初回警告を自動的に回避する挙動を持たない。
EV証明書
Extended Validation証明書
ベンダー提供SDK・専用ドライバー方式
産業用カメラや計測器などで、ベンダーがドライバーとSDKをセットで提供し、それ以外の使い方が事実上できないUSB機器接続方式。
ベンダーSDK
bitness一致要件
ネイティブモジュール(in-procのCOMコンポーネント、P/Invokeで呼ぶDLL、ベンダー提供SDKのDLLなど)を読み込むホストプロセスと、そのモジュールのビット構成(32bit/64bit)が一致していなければならないという制約。
32bit/64bitの一致
USB接続方式の選定は、デバイスマネージャーでその機器がどのUSBデバイスクラスとして見えるかを確認するところから始まる。
公式の選定順序は単純な方式から検討するもので、複数アプリの同時アクセスが不要ならWinUSBをUMDFドライバー開発より先に検討する。
UMDFドライバーで対応できない場合に初めてKMDFドライバーの開発を検討するという順序になっている。
複合デバイスはUsbccgp.sysによって機能ごとに別々のUSBデバイスクラスを持つデバイスとして展開される。
仮想COMポート方式は、CDC(通信クラス)というUSBデバイスクラスに標準ドライバーUsbser.sysが自動対応することで成立する。
HID方式は、HIDというUSBデバイスクラスにWindows標準のHidclass.sys/Hidusb.sysが対応することで、ドライバー配布なしに成立する。
Vendor Specific(FFh)など標準クラスに属さない機器では、WinUSBの利用がMicrosoftの推奨になる。
INFの配布なしにWinusb.sysが自動的に機能ドライバーとして載るのは、機器のファームウェアがMicrosoft OS記述子を持ち互換IDとしてWINUSBを報告する場合に限られる。カスタムINFを配布する経路ではOS記述子は不要。
WinUSBは複数のアプリケーションが同時に同じ機器へアクセスすることをサポートしない。
複数アプリが同時に同じ機器へアクセスする必要がある場合は、UMDFドライバーの開発が推奨される。
COM番号は機器の識別子ではないため、同型機を複数つなぐ構成や特定の1台を選ぶ必要がある場合は、VID/PID・シリアル番号から実行時にCOM番号を引き当てる一意識別を実装する。該当する1台しか繋がない構成や利用者に選ばせる設計では必須ではない
複合デバイスでは複数の機能が同じVID/PID・シリアル番号を共有するため、特定の機能を選び分ける必要がある場合はUSBインターフェース番号などを含めた一意識別が要る。該当する1台しか繋がない構成や利用者に選ばせる設計では必須ではない
HID方式で特定の1台を選び分ける必要がある場合は、VID/PID・シリアル番号(またはUsage Page/Usage)を用いて一意識別する。該当する1台しか繋がない構成や利用者に選ばせる設計では必須ではない
WinUSB方式で特定の1台を選び分ける必要がある場合は、デバイスインターフェースパスを鍵にして一意識別を行う。該当する1台しか繋がない構成や利用者に選ばせる設計では必須ではない
マウス・キーボード・タッチスクリーン・ペンに相当するトップレベルコレクションはWindowsのRaw Input Managerが排他で開くため、HIDアプリからの読み書きができない。
同期I/Oでは機器が応答しないとスレッドごと固まるリスクがあるため、WinUSBの読み取りはFILE_FLAG_OVERLAPPEDで開いた非同期I/O(Overlapped I/O)で回すのが実務上の通常の実装方法である(同期I/Oも可能)。
WinUSBの非同期読み取りでは、転送できたバイト数をWinUsb_GetOverlappedResult(GetOverlappedResultに相当する仕組み)で取得する。
WinUSBのセッション後始末では、CancelIoExで処理中のI/Oを止めてから完了を回収し、ハンドルを解放する。
PnP通知の購読は取り外しの検知に役立つが、それだけでは足りず、すべての読み書きの完了パスで削除系エラーをセッション終了として扱って初めて機器切断によるハンドル無効化への対処になる。
USB機器のPnP通知を受け取りたい場合、WinForms/WPFなど従来型デスクトップアプリでは、RegisterDeviceNotificationとWM_DEVICECHANGEの組み合わせで受け取る。
WinUSBでのUSBセレクティブサスペンドは、拡張プロパティOS機能記述子(またはINF)のDeviceIdleEnabledなどの設定で構成できる。
独自のINFを含むドライバーパッケージを配布する場合は、Microsoftのwinusb.sysを参照するだけのINFであっても、カタログファイルに署名がなければDriver Storeにステージされない。MS OS記述子でWINUSB互換IDを提示してinboxのwinusb.infにバインドさせる経路では、独自パッケージの配布自体が不要になる。
Windows 10 バージョン1607以降、新規のカーネルモードドライバーはDev Portal(Partner Center)経由のMicrosoftによる署名がなければロードされない。
Partner Centerのアカウント開設にはEVコード署名証明書が必要になる。
デバイスマネージャーで既にベンダー製ドライバーが載っている機器は、方式Dであるベンダー提供SDK・専用ドライバーの対象になる。
ベンダーSDKのDLLが32bit専用でビルドされている場合、64bitアプリのプロセスは32bit専用DLLを直接呼び出せず、bitness一致要件を満たす必要がある。
USB-シリアル機器はケーブルを抜くとポートが消えるため、抜き差しに対する耐性を持たせたい場合は、PnPの取り外し通知を受けてからポートをCloseする実装が安全とされる(実装しなくても直ちに動作不能になるわけではないが、抜去時に例外でアプリごと落ちる事故につながる)。