C#/Win32API覚書
Windows Programming
主にC#ベースで作成します.速度を求めるならnativeコードで実装すべきでしょう.とりあえずC#で作って,速度を稼ぎたいところでunsafe codeを書いていきます.
その時点でWindows APIの使用となり,Import宣言等を自前で書く必要が出てきます.
それで我慢できなくなったら,VC++等への移行も検討しましょう.
主にC#ベースで作成します.速度を求めるならnativeコードで実装すべきでしょう.とりあえずC#で作って,速度を稼ぎたいところでunsafe codeを書いていきます.
その時点でWindows APIの使用となり,Import宣言等を自前で書く必要が出てきます.
それで我慢できなくなったら,VC++等への移行も検討しましょう.
デバイス設定:断りの無い限り,下記のとおりとする.
item | value | remark |
---|---|---|
Poling cycle | 10 mSec | Interface Descriptorで指定 |
EndPointBuffer size | 64 bytes | Interrupt transfer |
Report Descriptorで定義したサイズは固定長である.
Report Descriptorでは,Variable....という記載があったので,デバイス→ホスト間の転送サイズは任意であると考えていた.実際に動かして見ると調子が悪かったので,HIDは 原則固定長であると認識した.
実際にDevice/Host間で送受信データサイズを変えて確認した.ただし,いずれもデバイスのReport Descriptorでは 8bit 64count(64octet)とし,Interface Descriptorでは,EP sizeを64octetとしている.
Transfer direction | from | to | Remark |
---|---|---|---|
IN transaction | DEV: 64byte送信 | HOST: 64Bytes受信 | OK |
OUT transaction | HOST: 64byte送信 | DEV:64byte受信 | OK |
IN transaction | DEV: 1byte送信 | HOST: 64Bytes受信 | NG |
IN transaction | DEV: 1byte送信 | HOST: 1Bytes受信 | NG |
IN transaction | DEV: 64byte送信 | HOST: 1Bytes受信 | NG |
Full Speed時は64byte(max)のユーザデータを送受信できるが,固定長となる.UARTの代替として使用する場合,もしくは64octetを超えるパケット通信の土管として使用する場合には,さらに一段ラッパーをかます必要がある.Low Speedもサポートするのであれば,パケットサイズが8octetになる.
struct { Byte SizeOfData ; BYte Data[63] ; // Low=63, High/Full=63 }
PC側は,Report Descriptorを受け取って,capabilityとしてサイズを認識するので,データサイズは固定としない.デバイス側で,最大長となるように設定する.デバイスのメモリの都合・データ流量の都合により,削減する場合は,descriptor・interrupt transferの転送サイズをReport通りに修正するだけでよい.
Interrupt転送は,デバイス-ホスト間でのデータ取りこぼしが起こりえないという話をどこかで見かけた.本当にそうなのかを検証してみた.*1
Poling Timing(Report) | PO cycle実測 | 結果 | Remark |
---|---|---|---|
10mSec | 8mSec | 取りこぼしなし | 周期が早いのは..? |
1mSec | 2mSec | かなりこぼす | 複合デバイスにしたので,最速では回らないか? スレッドで0秒寝かせているのが悪いのかもしれない.回りっぱなしでもトライする?. |
2mSec | 2mSec | かなりこぼします | 10/Sep.の追試実験用.条件よければOK. |
※試験結果を随時追加していく.★疑問:GetInputReportBufferSize は なに?EPサイズでもreportサイズでもない.. reportの記憶回数でもない..
同時期に,C++ Builderでホストプログラムを作成されている方より,取りこぼしはおきない旨,伺いました.Visual C#が癌なのかという疑いが出てきます.
前回の測定時のフローを示す.
1. 受信を Threadクラスを使ってぶん回す.ひたすらRead()を呼び出し続ける.
2. 受信したデータは,下記のクラスを用いてデータを積み上げる.
System.Collection.Generic.Queue<Byte>
このとき,スレッド内では,追加時にlock(Que)する.
3. Formスレッドで,上記クラスからenqueしてデータを取り出し,表示用のListControlに追加する.
このとき,lockなし,連続して吸い出している.
Formスレッドで,Queから読み出すたびに,Thread.sleep(0)を入れた.体感もっさりした気がする*3.取りこぼしは消えない.
MSVSのmanualを見ると,動的に記憶領域を延ばすと書かれていた.追加時のオーバヘッドでもこぼしている可能性があるのだろう.
また,ロックの仕方に問題がありそう.本質的にスレッドセーフではないとあるので,記載どおりのlockを行う.
・・・ために,generic Queueではなく,Collection.Queueクラスを用いる.
ヘルプに記載のとおり,下記のlock()を適用する.
lock(myCollection.SyncRoot)
System.Collection.Generic.Queue<>ではなく,System.Collection.Queueを使ってみた.読み書き両方でlock()を使ったが,やはり駄目だ.
Byte[]の動的確保にも負荷がかかっているのだろう.開放処理もGCが走るだろうし.
表示させずにデータ取得させてから,表示してみる.
取りこぼしナシ.CPU負荷がそれなりにかかる模様.*4
アプリケーション走行速度・データの扱い方による問題と思われる.
また,受信バッファは512byteまで指定可能のようだ.でかい値を放り込んでも拒絶される(32byte:report値に戻る?).
ただし,これによる救済措置が図れるのかは疑問である・・・.
実は,チェキ1が終わった段階で,USB Snoopy Proを使用してPC側にデータが来ているかどうかを確認していた.
案の定,データが来ていることは見て取れたので,取りこぼしているのはドライバ~アプリのアタリであるとアタリがついていたのです.
そもそもHIDと言っている以上,ヒトの応答速度程度の情報を送受信するのが目的であるし,仕様なのかもしれない.
また,Report Descriptorを見ても判るように,固定長・ビット単位で意味を持たせたデータを送るのが目的であり,今回のようにストリームデータを送るのは想定外使用なのだろう.もし,確実に双方向のデータ通信を行いたいのであれば,ホスト~デバイス間でハンドシェイクを行う必要があるでしょう.
そこまでするならBulk転送で~とも思うのだけれど,それはそれでホスト側のドライバが面倒.汎用USBドライバやWinUSBといったものがあるようなので,それらの使用も視野に入れたい.
ひとまずHIDポート制御部分を切り出してコンポーネント化しておきたいところですね.
★本気時は書きかけのものです.頓挫する前に公開★
氏のsample"generichid_cs"から,必要なコードを流用する.C#では,Win32APIの呼び出しを直接行うことはできない.unsafe codeとして,DLLからAPIをimport宣言してやる必要がある.VBでWin32APIを用いるために宣言していたのと同様である.*1
console applicationでもよさそうだが,今後のことを考えてGUIで作っていく.Formに配置するリソースを列挙する.
下記のファイルをコピーする.ただし,VBのnamespaceも使ってるようなので,少し綺麗にしたいと思た.ちうことで,VBのlength/CRLFだけを使っていたので,これを排除しただけなんですがね.
Include all functions of HostTest_01.
WM_DEVICECHANGEメッセージが通知される模様.標準でフックできないようなので,WindowProcedureをoverrideしてsniffする.
登録: RegisterForDeviceNotifications() 停止:StopReceivingDeviceNotifications()
WM_DEVICECHANGEメッセージを受けて,pathnameとマッチするかチェックする.マッチする場合はtrueを返す.
Uses SetupDi API functions to retrieve the device path name of an attached device that belongs to an interface class.
SetupDi APIを使って,interface classに属する接続されたdevice path nameを検索する.
★MSDNより,C#で Num Text boxの作成 - 派生させてHex Edit boxでも作れ..~ 入力: hexのみ, 出力:':'区切りで表示, nibbleでfocus失うなら捨てる. 入力中は文字色変えるとか.
http://msdn.microsoft.com/ja-jp/library/ms229644(VS.80).aspx http://msdn.microsoft.com/en-us/library/ms790920.aspx http://msdn.microsoft.com/ja-jp/library/cc429201.aspx
HIDとして,キーボードとマウスは抑えておきたいところである.そこで,キーボードについて,仕様ではよくわからなかった動作について確認する.PCやOSに依存するかもしれないので,ここで記述する事項は実際に試したこと,推測(妄想)事項であることを明記しておく.
何らかの明確な動作に関する資料が開示されているのであれば,ポインタを教示いただきますと幸いです.
先日の疑問について,実装して確認する.
デバイス側のマイコンから,キーコード(usage ID)を送信し,PC側でどういう挙動を示すかを調べる.
(1) {0x00, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (2) {0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (3) {0x00, 0, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A }, (4) {0x00, 0, 0x07, 0x08, 0x09, 0x0A, 0x05, 0x06 }, (5) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // nothing (6) {0x00, 0, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00 }, // space 結果. "abcdefg "の繰り返しとなる. 456789a "押している"情報であることがわかる. (1)-(2)で 'a'を2回送っていることになるはず.(2回数はshift押下) (3),(4)で並びを変えてみたが, 反応ナシ. (5)で全部離したと認識して, "bcdefg"をinput. このとき,押下情報(3)の若い順に入力されたと考えられる.(並びを変えて確認要) また,別キーボードにてshiftを押下したところ,押下中は文字サイズが変わった. # 教訓.スキップせずにコツコツと進みましょう:) "abcdefg ".... (1) {0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (2) {0x00, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (3) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // nothing (4) {0x00, 0, 0x07, 0x08, 0x09, 0x0A, 0x05, 0x06 }, (5) {0x00, 0, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00 }, (6) {0x00, 0, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00 }, "adefgbc "で繰り返された. (1) 'a'を押下, (2) 'a'を押下しながら shift押下 (3) 'a'とshiftを同時に離す. → (shiftはキーの前後でon状態を保つ必要がある? 少なくとも入力時に同時か一つ前?) (4) d,e,f,g,b,c を押下 (5) 'g'だけ離す... (6) b-fを離して, spaceを押す. (1) {0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (2) {0x01, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // shift入れてみる (3) {0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (4) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // shift入れてみる (5) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // nothing (6) {0x00, 0, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00 }, (7) {0x00, 0, 0x05, 0xC6, 0xC4, 0x00, 0x00, 0x00 }, (8) {0x00, 0, 0x05, 0xC6, 0x00, 0x00, 0x00, 0x00 }, (9) {0x00, 0, 0x05, 0xC6, 0xC4, 0x00, 0x00, 0x00 }, (A) {0x00, 0, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00 }, ^a ^a def 0xC4/0xC6は無視されたぽいな.. b <space> !ここでReport descriptorのUsage Maxが 0x65なのに気づく. 0xE7とすることで,定義値全て有効になるはず.. (1) {0x02, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (2) {0x02, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // shift入れてみる (3) {0x02, 0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, (4) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // shift入れてみる (5) {0x00, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // nothing (6) {0x00, 0, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00 }, (7) {0x00, 0, 0x05, 0xC6, 0xC4, 0x00, 0x00, 0x00 }, (8) {0x00, 0, 0x05, 0xC6, 0x00, 0x00, 0x00, 0x00 }, (9) {0x00, 0, 0x05, 0xC6, 0xC4, 0x00, 0x00, 0x00 }, (A) {0x00, 0, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00 }, ならなかったなぁ. AAdefb "Shift"は同時に離しても,跡で開放でも良いみたい. min.max.を 0x80 - 0x7Fにしてみた. →値全部無視された.SHIFTのみに反応して,"固定キー機能"ダイアログが出てくる. 0x00 - 0x00E7にしてみたが,駄目だな. keypadになると,usage sub-IDが変わってくるので,無視されるのだろう. table中,keyboard/keypadと明記されていたのはコレによるものと思われる. 分けてくれよ...
usage IDとASCIIコードとはテーブル引きするしかなさそう.US keyboardをベースにしているらしく,日本語keyboardとは異なるマップと考えるしかない.また,SHIFT/CTRL等の情報は文字とは別です.
前述の結果より,本I/FでPCが受信するのはキーの押下情報であり,入力された文字情報ではないです.文字入力の肩代わりをさせる場合,ほかのキーボードとの干渉を考慮する必要がありそうです.
ゲームの入力装置として考える場合は,押下情報であったことは良いことでしょう.同時押し6つまで認識してくれそうです.(SHIFT/ALT/CTRLは別腹で同時押し可)
ASCII文字 - 0x20を index0として,下記のIDを送ると良い.
ただし,"SHIFT+"は,SHIFTを押下しながら文字を入力した場合であり,単純に1倍との送信では実現できない.
0x2C, SHIFT+0x1E, SHIFT+0x34, SHIFT+0x20, SHIFT+0x21, SHIFT+0x22, SHIFT+0x24, 0x34, SHIFT+0x26, SHIFT+0x27, SHIFT+0x25, SHIFT+0x2E, 0x36, 0x2D, 0x37, 0x38, // 0x20~0x2F, → <SPC>!"#$%&'()*+,-./ 0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, SHIFT+0x33, 0x33, SHIFT+0x36, 0x2E, SHIFT+0x37, SHIFT+0x38 // 0x30~0x3F, → 0123456789:;<=>? SHIFT+0x1F, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, // 0x40~0x4F, → @ABCDEFGHIJKLMNO // 0x89 : Keyboard International3(17) => '\'キー 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x2F, 0x31 ,0x30, SHIFT+0x23, SHIFT+0x2D, // 0x50~0x5F, → PQRSTUVWXYZ[\]^_ 0x34, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, // 0x60~0x6F, → `abcdefghijklmno 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, SHIFT+0x2F, SHIFT+0x31, SHIFT+0x30, SHIFT+0x32, 0x2A // 0x70~0x7F, → pqrstuvwxyz{|}~<DEL>
PSoC環境を使う限り,自動的にInterface数を把握して,standard requestに応答してくれる模様.便利ですな.. *1
メモ
PSoCのEPは,入出力で同じIDが使えない?simple IOの実装でEPを1/1または3/3としたときに,IN応答が無かった.OUTを受けてからIN transactionを起こしていたので,先に処理したモン勝ちか?詳細はPSoCのTRMで確認する予定.
【宿題】の結果,下記の記述が見つかりました.
PSoC TRM, Document No. 001-14463 Rev. ** 34. Full-Speed USB The SIE supports five endpoints including a control endpoint (endpoint 0) and four data endpoints (endpoint 1, 2, 3, and4). The control endpoint can be configured to support SETUP, IN, and OUT requests. The data endpoints can be individually configured to respond to Interrupt, Bulk, or Isochronous IN or OUT requests.
ということで,EP1~4は,INかOUTのいずれか1つしか取れません.したがって,インタフェースIN/OUTを2つ用いるデバイスでは,EPも2つ消費します.入出力対応しているEPであれば,EP数を削減できたのですが.まぁ,物理的な制約であり,仮に入出力OKでも,IDが経るなら同じなので,こんなもんなんでしょう.
キーボード,マウス,+α(入出力)とするならば,キーボードのLED出力は受け取れないということですね.
PID/VIDが同じだと,Windows Device ManagerがInterfaceを覚えているようだ.
複合→単純に変えたとき,2度目以降は複合として扱われた.
Device Manager → USBコントローラ の 該当するVID/PIDの複合デバイスを削除することで,再度認識してくれるみたいです.
wiki機能でコンテンツにしているけれども,まとまってないな...
日々書き散らかしてまとめていないからかなぁ.使い方間違ってる?w
階層だてて理解していく必要があるが,とりあえずはメモレベルで残す.
Usage Page | データの使い方または機能の定義 |
論理的最小値 | アイテムがレポートする値の最小値 |
論理的最大値 | アイテムがレポートする値の最大値 |
物理的最小値 | 論理的最小値を物理的最小値で示したもの |
物理的最大値 | 論理的最大値を物理的最大値で示したもの |
3.1 HID Usage Table Conventions 32'hxxxx ::= { Usage Page(16bit) , Usage ID(16bit) } 3.3 Usages and Units When declaring Units for a main item, the Logical Minimum, Logical Maximum, Physical Minimum, Physical Maximum, and Unit Exponent items must also be declared. 3.4 Usage Types control type( LC(Linear Control),OOC(On/Off Control),MC(Momentary Control)など)により, Logical Min - Maxの範囲設定例と動作概要の説明 3.4.1 Usage Types (Controls) Linear Control(LC) On/Off Control(OOC) Momentary Control(MC) One Shot Control(OSC) Re-trigger Control(RTC) 3.4.2 Usage Types (Data)
Type | Flags | Description |
---|---|---|
Selector (Sel) | Array | Contained in a Named Array (NAry). |
Static Value (SV) | Constant, Variable, Absolute | A read-only multiple-bit value. |
Static Flag (SF) | Constant, Variable, Absolute | A read-only single-bit value. |
Dynamic Value (DV) | Data, Variable, Absolute | A read/write multiple-bit value. |
Dynamic Flag (DF) | Data, Variable, Absolute | A read/write single-bit value. |
4 Generic Desktop Page (0x01) |* Usage ID |* Usage Name |* Usage Type |* Section8 Hut1_12.pdf 4.3.1 Resolution Multiplier
Effective Resolutiuon Multiplierは,以下の式で求められる.
where RMV = Resolution Multiplier Value, LMin = Logical Minimum, LMax = Logical Maximum, ssPMin = Physical Minimum, and PMax = Physical Maximum
Collection (Logical) Usage Page Generic Desktop (0x01) Usage Resolution Multiplier (0x48) Logical Minimum 0 Logical Maximum 15 Physical Minimum 1 Physical Maximum 16 Report Size 4 Report Count 1 Feature (Data, Var, Abs) <- featureコマンドで, 1-16を受け付ける. Usage Page Generic Desktop (0x01) Usage (Wheel) (0x38) Logical Minimum -127 Logical Maximum 127 Report Count 1 Report Size 8 Input (Data, Var, Rel) End Collection ||< *参考-usb.orgの仕様から HID Report Descriptorを消化してみる. どう読めばよいか,という視点で確認. [http://www.computer-engineering.org/index.php?title=USB_HID_Notes:ここ]も参考にした. キーボードの例 >|| Usage Page (Generic Desktop), Usage (Keyboard), Collection (Application), Report Size (1), Report Count (8), Usage Page (Key Codes), // KeyCodeを示すことを宣言 Usage Minimum (224), //ID224-231を size=1(bit) x 8個 に割り当てる. Usage Maximum (231), // Logical Minimum (0), //押下状態0/1を定義 Logical Maximum (1), // Input (Data, Variable, Absolute), ;Modifier byte Report Count (1), Report Size (8), Input (Constant), ;Reserved byte Report Count (5), // 5個 Report Size (1), // 1bit Usage Page (LEDs), // Usage Minimum (1), // 1~5 Usage Maximum (5), // Output (Data, Variable, Absolute), ;LED report Report Count (1), Report Size (3), Output (Constant), ;LED report padding Report Count (6), // 6個 Report Size (8), // 8bitサイズ Logical Minimum (0), // 0~ Logical Maximum(255), // 255 Usage Page (Key Codes), // KeyCodeを示すことを宣言 Usage Minimum (0), // 0~ Usage Maximum (255), // 255 このUsage min/maxと冒頭のmin/maxでかなり意味が違う気がする. Array型だからいいように把握してくれるのかな.. Input (Data, Array), // 反映. End Collection
1バイト目でUsage Min/Maxを指定しているのは,下記押下状態を通知するためと考えられる(未確認)
Usage ID(Dec) | Usage ID(Hex) | Usage Name |
---|---|---|
224 | E0 | Keyboard LeftControl |
225 | E1 | Keyboard LeftShift |
226 | E2 | Keyboard LeftAlt |
227 | E3 | Keyboard Left GUI |
228 | E4 | Keyboard RightControl |
229 | E5 | Keyboard RightShift |
230 | E6 | Keyboard RightAlt |
231 | E7 | Keyboard Right GUI |
Usage(LED)'s valueは以下のとおり.これをデバイス側で受けて,LEDのon/off制御をおこなう..のだろう.
01 | Num Lock |
02 | Caps Lock |
03 | Scroll Lock |
04 | Compose |
05 | Kana |