C# でHID USBデバイスにアクセスするアプリケーション実装

# 概要

Windows Hostのユーザアプリケーションから、HID USB デバイスにアクセスする方法。
C# での実現方法を一通りまとめる。

# HIDのデータ転送

## HID・ディスクリプタ

USB規格では，各デバイス固有の情報を定められた形式でコード化したものをディスクリプタと呼んでいる。ホストからのリクエストに応じて、デバイスはディスクリプタを送信する。

* デバイス・ディスクリプタ
* コンフィグレーション・ディスクリプタ
* インターフェース・ディスクリプタ
* エンドポイント・ディスクリプタ

さらにHIDクラス規格のみ定義されるディスクリプタが存在している。

* HID・ディスクリプタ
* レポート・ディスクリプタ
* フィジカル・ディスクリプタ

HIDクラスではレポートと呼ばれる単位でデータを転送するが、レポートの中にどのような情報がどのように並んでいるかは、レポートディスクリプタによって定義する。
レポートには、Input、Output、Featureの３つがありIn/Outはインタラプト転送だが、
FeatureレポートはEP0を使うコンフィグ用の為、コントロール転送となっている。

## クラスコマンドでのレポート操作

USBのクラスごとに定められているコマンドをクラスコマンドという。
HIDクラスのクラスコマンドの中でも、下記の２つを使えばコントロール転送を使ってレポートの送受信が可能になる。

* GET_REPORT
* SET_REPORT

これらはドライバにあるAPIを利用することで実現可能である。


# デバイスの選択

デバイスによってアクセスできる内容が変わってくるため、
まずは目的によって、どのようにアクセスするか決める必要がある。
何のデバイスとして認識するかはデバイスディスクリプタと、適用するドライバに依存する。

###  キーボード等のHIDデバイス

標準ドライバが適用されないようなHIDデバイスであれば、
Openして、GET_REPORT、SET_REPORTすることは可能である。
キーボードやマウス等の一般的なHIDデバイスは標準ドライバが適用されるので、
基本的に制御はできないが、libusb等の別のドライバを適用させれば、出来るようになる。
これらの制御はライブラリやOSのapiから操作できる。

### WinUSB デバイス

Windows Vista 以降すべてのバージョンの Windows に含まれている。
Windowsの汎用ドライバはAPIを提供しており、MSDNで仕様が公開されている。
ほとんどのデバイス操作が可能になる。
デバイスのディスクリプタによって認識させることが出来るため、汎用性は高い。


###  その他
DeviceIOControl (コントロール転送、RESET), ReadFile/WriteFile（バルク、インタラプト転送）
これらを使って、USBの標準リクエスト・ベンダリクエストの発行や任意のEPのRead/Writeが可能になる。
アプリケーションからのデータ転送の流れは、ドライバオープン->デバイス通信->ドライバクローズの3ステップ。
もし仮にデバイスドライバで動作している場合、システムに予約されているため、そのままではアプリケーションからはアクセス不可。ドライバを作成して適用するか、デバイスドライバで動作しないようにデバイスを工夫する必要がある。


# ライブラリを使う
## winUSB

WinUSBは、Windowsに存在するドライバフレームワークになっていて、これを使えば独自のドライバ作成が不要になるというものだが、多くの場合、使用できないと思う。
WindowsがデバイスをWinUSBデバイスとして認識するには、デバイス側ファームウェアでデバイスディスクリプタを、HIDデバイスではなくてWinUSBデバイスとして定義する必要がある。
あくまでも、独自のデバイスを作りたい場合のための汎用的なドライバと認識している。

## HIDAPI

GET_REPORT、SET_REPORTでデータを送信したい場合、Signal11 のHIDAPIが良い。
動作の仕組みはWindows OSの下で, HIDAPIを使用してバックエンドとしてlibusb, hidrawが使用可能になる。
HIDの扱いを抽象化しているそうなのでWin/Linux両対応のコードが書けるそうだ。
これを早速nugetした後、api.dllの出力を新しい場合はディレクトリにコピーするようにする。
exampleを実行したところ、期待どおりのデバイスリストが見えている。
https://github.com/nvchernov/hidapiadapter

リスト取得後はREADMEを参考に、Get ReportとSet Reportを発行できるようだ。[README.txt](https://github.com/signal11/hidapi/blob/master/linux/README.txt)
手っ取り早く動作させたい場合には便利だと思う。

## libusb

libusb.NETをnugetして、下記のexample を実行したが、Alldevicesの戻り値が入ってこない。
http://libusbdotnet.sourceforge.net/V2/Index.html

libusbはホストがデバイスにアクセスする前に
独自のフィルタドライバを介して送受信をコントロールすることを行っているらしい。
だからフィルタに登録することで既存のHIDデバイスなんかも操作可能だし、アプリで扱う場合はlibusbだけでUSB機器を操作することも出来そうだ。

### Filter driver

試しにHIDキーボードをフィルタに登録してみた。
下記からlibusb-win32をダウンロードする。
https://sourceforge.net/projects/libusb-win32/
bin/inf-wizard.exeを使えば、専用のドライバとしてインストール可能だがinstall-filter-win.exeを使えばフィルターして扱うことが可能になる。
Cygwinにlibusb-develをインストールした後、下記のコードを-lusbでビルドして実行したところ、デバイスが見れるようになった。

<pre>
 #include "usb.h"
 #include "stdio.h"
int main() {
        usb_init();
        usb_find_busses();
        usb_find_devices();

        for (struct usb_bus* bus = usb_get_busses(); bus; bus = bus->next) {
                for (struct usb_device* dev = bus->devices; dev; dev = dev->next)
                        printf("0x%04x 0x%04x \n", dev->descriptor.idVendor, dev->descriptor.idProduct);
        }
        return 0;
}
</pre>


そのままでは気持ち悪いので、プロパティ->ドライバ->削除を行ったらフィルタドライバが消えた。
この状態でまたUSBデバイスを挿せば、デフォルトのHIDドライバで動作する。なるほど。
Unixで使いたい場合に有効かも知れないと思い、せっかくなので
[インタラプト転送で読んだ値をチェックするコード](https://github.com/hirakisp/keychk)を書いた。

# Winapiを使う

ということで、ライブラリを使う場合は、HIDAPIを使うのがおすすめではあるが、
できるのであればWin32apiを使う方が汎用的に作れるのではないか。
これは、情報が多いため割愛する。（Jan Axelson を参考するとよさげ）

# ドライバでの実装

## 参考
DeviceIOControl を使った ベンダリクエストの転送については[Cypress社のezusb.sys](https://www.cypress.com/comment/300231)を参考にした。
ezusb.sysではVBアプリケーションからWindows-APIを実行するためのezusb用ライブラリを提供しており、
ソースコードも公開されている。
インストール後、ソースがC:\Cypress\USB\Drivers\ezusbdrvに展開されている。
これを読めばVendorRequestからCallUSBD -> DeviceIoControl が実行されるまでの一連の動作がなんとなく見えてくる。
ライセンスがあるためここにはソースを掲載出来ないがCQ出版のUSB汎用インターフェース・キットに一連のAPIの実装と説明が掲載されている。

## 環境 (WDK)

Windows10向けWDKでは、visual studioが要求されるが開発・テスト・デプロイツールがオールインワンということらしい。
[Windows7までのWDK](https://www.microsoft.com/en-us/download/details.aspx?id=11800)でも、Free / Checked build environment で build コマンドでビルドは簡単にでき、infファイルからCatalogを作るinf2catやinfの整合性チェックするchkinf.bat等のツール類が存在した。
問題はtest機能の生産性の低さにあった。
まだ使っていないがWindows10向けSDKでは、これが改善されていると良い。