USBプロジェクト - WindowsでHIDデバイスを扱う定石 (1) [USB]
ようやく、HIDデバイスを扱うためのPC側アプリケーションが書ける様になったので、HIDデバイスを操作するときの定石についてまとめておきます。 テキストは、EDNの記事、 Using the HID class eases the job of writing USB device drivers です。 日本語版が見つけられませんでした。 もしかしたら、日本には上陸しなかったのかな?
HIDは、ヒューマン・インターフェースのためにあるわけではない
この記事の言わんとするところは、「ヒューマン・インターフェース・デバイス(HID)」という言葉に惑わされること無く、なんにでも使ってしまえというところです。 実際、単純な入出力なら、ボタン入力とLED出力に偽装すれば、どんなものだってHIDで十分に実用になります。 しかも、HIDを使うだけで、プログラム開発が圧倒的に簡単になります。
STEP1 : HIDを探せ
HIDデバイスを扱うための第一歩は、HIDドライバを探すことです。 このステップは簡単です。 HidD_GetHidGuid()という関数を呼び出すとHIDデバイスの識別子(GUID)が入手できます。
関数を呼び出すために必要なこと
この関数は、hid.dllという動的リンク・ライブラリ(Dynamic Link Library : DLL)に入っています。 通常は、C:\WINDOWS\SYSTEM32ディレクトリを探すと見つかります。 そのため、「hid.dllにあるHidD_GetHidGuidを呼び出せ。」とプログラムに書けばすべて解決です。
ところが、いわゆる高級言語でプログラムを作成する場合、「HidD_GetHidGuidは、hid.dllに入っている。」と教えてやる必要があります。 Visual C++ でプログラムを作成する場合、この情報を持っているのが「LIBファイル」です。 そのため、プログラム開発には、関数がすぐそこにあるにもかかわらず、「Windows DDKに入っているhid.libファイル」を取り寄せなくてはなりません。 さらに、「HidD_GetHidGuidは、こんな風に使う。」という情報が「ヘッダファイル」という別のファイルに入っています。 そのため、「Windows DDKに入っているhid.hファイル」を取り寄せなくてはなりません。 このようにVisual C++では、必要なファイルが芋づる式に増えていきます。
一方、Visual BASICの場合はどうかというと、実は、「HidD_GetHidGuidは、こんな風に使う。」という情報を入れたファイルがそもそも存在しません。 では、どうするかというと、自分で書き起こすのです。 でも、ご安心ください。 世界中の人たちが「Visual BASICで関数を使いたい。」と考えているので、ありがたいことにその成果を利用させてもらうことができます。 私がプログラムのお手本にした"hidclass_vs5"の場合にも、関数呼び出しの方法を書いたファイル"HidDeclaration.vb"が用意されています。
<DllImport ("hid.dll")> Sub HidD_GetHidGuid _ (ByRef HidGuid as System.Guid) End Sub
必要なのは、これだけです。 しかも、この関数がどの"DLL"ファイルに入っているかという情報も含まれていますので、これだけの記述で十分なのです。 これと同じことがVisual C++でも出来ればよいのですけど。
というわけで、Visual BASICを使うときには、ライブラリ相当のテキストファイルが必要だけれど、Windows DDKを入手する必要はありません。
STEP2 : HIDクラスのデバイスをリストアップしろ
HIDクラスに所属するUSBデバイスは、オペレーティング・システムの作用により、PCに接続されたとたんにHIDドライバの管理下に置かれます。 そのため、HIDドライバに問い合わせをすると、現在接続中のHIDクラス・デバイスのリストを入手することができます。
このリストの問い合わせに使用される関数が、SetupDiGetClassDevs()です。 この関数は、"setupapi.dll"に入っています。 関数の宣言は、"DeviceManagementDeclarations.vb"を見てください。
この関数は、HIDクラス・デバイスにたどり着くための鍵のリストを返します。
STEP3 : 鍵からパス名を割り出せ
出来上がったリストから、アプリケーションが操作しようとしているデバイスを割り出さなくてはならないのですが、鍵にはデバイスを割り出すための詳細な情報は含まれていません。 このため、デバイスを特定する名前である「パス名」のリストに変換します。
パス名を求めるためには、関数SetupDiEnumDeviceInterfaces()とSetupDiGetDeviceInterfaceDetail()を使用します。 まず、SetupDiEnumDeviceInterfaces()でインターフェースを示す構造体を構築します。 そして、SetupDiGetDeviceInterfaceDetail()でその詳細なデータを引き出します。 何で、こんな二段構成になっているのかは、理解できません。
SetupDiGetDeviceInterfaceDetail()は、二回呼び出されます。 一回目は、得られるデータの大きさを調べるために呼び出されます。 そして、二回目は、あらかじめ十分な大きさの領域を確保して、そこにデータを書かせます。 領域を確保するには、ヒープ領域が便利です。 ヒープに書かせたデータをVisual BASICのStringにコピーしたら、ヒープは開放されます。
Visual BASICでヒープを割り当てる
Cの場合、ヒープを割り当てるためには、"malloc()"のような関数が使用されました。 ところが、ちょっと前のVisual BASICの場合、もともとヒープのような概念がないため、 何とか誤魔化すしか方法がありませんでした。
現在のVisual BASICは、かなりCに歩み寄っているようで、"Marshal.AllocHGlobal()"という関数(メソッド?)が提供されています。
長くなったので、続く…
あらためて目から鱗です。
HIDクラスに限定したJB8、JB16用のプロセッサエキスパートとか在れば、デバイス側プログラミングも一気に楽になりそうですね。
by hamayan (2008-03-12 09:11)
プロセッサ・エキスパートのビーンを作るには、"Professional Edition"が必要です。ぜひ、"Design Challenge"の選考に残って、手に入れてください。
というか、そのくらいはマイコン・メーカが提供しても良さそうなものだが。
by noritan (2008-03-12 11:26)