2006年05月20日

Raw Input(直訳:生入力)

本日は、Visual Studio® User Group より、複数のキーボードを別処理するには というスレッドを話題にしようと思います。飽くまで簡単に。

同じタイプの複数の入力デバイスからの入力を受けたい、と言うのは、例えばゲームパッドでは良くある話でしょう。ですが、これがキーボードやマウスとなるとなかなか要望も稀で解決策も難しい物があります。ついでに、使いこなすのも大変そうです。

基本的なところでは、Windows はそれらを扱いません。今使っているノートパソコンにはタッチパッドが付いていて、かつ USB でマウスも接続していますが、この両者はごくナチュラルに同居しており、どちらでもカーソルやボタンを操作できます。プログラム的にも、この両者からの入力は分け隔て無く .NET で言えば MouseDown とか MouseMove とかが発生します。そして、どちらからの入力なのかというのは考慮されず、透過的に扱うことを強制されます。

.NET のイベントや、その下層レベルのメッセージである WM_LBUTTONDOWN とか WM_MOUSEMOVE などで扱えないのならばどうするか。その解決になるかも知れない一つが、Raw Input に関連する API 群です。その端っこをちょっとだけ踏んでみます。

言葉通り、生の入力を直接扱う API です。受け取り方にはポーリングで判断する方法と Windows メッセージでイベントドリブンを行う方法があり、後者は WM_INPUT メッセージで受信することができます。

しかしこれ、情報が(特に日本語の)ほとんど無いのでなかなか大変。取りあえず形にしてみましたが、まともに扱おうとするとかなりの手間が予想されました。そもそも、デバイスを識別するハンドルは手に入りましたが、これをどう使うのかさっぱり分かりませんし。

なお、今回のコードは Windows XP 以降限定です。

 // C# のコード。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class RawInputForm : Form {
    [STAThread] public static void Main() {
        Application.Run(new RawInputForm());
    }
    private TextBox textBox = new TextBox();
    public RawInputForm() {
        this.textBox.Dock = DockStyle.Fill;
        this.textBox.Multiline = true;
        this.textBox.ScrollBars = ScrollBars.Vertical;
        this.Controls.Add(this.textBox);

        int size = Marshal.SizeOf(typeof(RawInputDevice));
        RawInputDevice[] devices = new RawInputDevice[1];
        // UsagePage=1,Usage=2 でマウスデバイスを指す
        devices[0].UsagePage = 1;
        devices[0].Usage = 2;
        //WM_INPUT を受け取るウィンドウ
        devices[0].Target = this.Handle;
        //WM_INPUT を有効にするデバイス群、devices の数、
        //  RawInputDevice の構造体サイズ
        RegisterRawInputDevices(devices, 1, size);
    }
    private void ProcessInputKey(ref Message m) {
        const int RidInput = 0x10000003;
        int headerSize = Marshal.SizeOf(typeof(RawInputHeader));
        int size = Marshal.SizeOf(typeof(RawInput));
        RawInput input;
        GetRawInputData(m.LParam, RidInput, out input,
                        ref size, headerSize);
        RawMouse mouse = input.Mouse;
        this.textBox.AppendText(
            //デバイスの番号と直前からの移動量を表示
            string.Format("{0}({1},{2})\r\n",
                          input.Header.Device,
                          mouse.LastX, mouse.LastY));
    }
    protected override void WndProc(ref Message m) {
        const int WmInput = 0xFF;
        if (m.Msg == WmInput)
            this.ProcessInputKey(ref m);
        base.WndProc(ref m);
    }

    [DllImport("user32.dll")]
    private static extern int RegisterRawInputDevices(
        RawInputDevice[] devices, int number, int size);
    [DllImport("user32.dll")]
    private static extern int GetRawInputData(
        IntPtr rawInput, int command, out RawInput data,
        ref int size, int headerSize);
    private struct RawInputDevice {
        public short UsagePage;
        public short Usage;
        public int Flags;
        public IntPtr Target;
    }
    private struct RawInputHeader {
        public int Type;
        public int Size;
        public IntPtr Device;
        public IntPtr WParam;
    }
    private struct RawInput {
        public RawInputHeader Header;
        public RawMouse Mouse;
    }
    private struct RawMouse {
        public short Flags;
        public short ButtonFlags;
        public short ButtonData;
        public int RawButtons;
        public int LastX;
        public int LastY;
        public int Extra;
    }
}

' VB.NET のコード。
Imports System
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class RawInputForm
    Inherits Form
    
    Public Shared Sub Main()
        Application.Run(New RawInputForm())
    End Sub

    Private m_textBox As New TextBox()

    Public Sub New()
        Me.m_textBox.Dock = DockStyle.Fill
        Me.m_textBox.Multiline = True
        Me.m_textBox.ScrollBars = ScrollBars.Vertical
        Me.Controls.Add(Me.m_textBox)
        
        Dim size As Integer _
             = Marshal.SizeOf(GetType(RawInputDevice))
        Dim devices(0) As RawInputDevice
        ' UsagePage=1,Usage=2 でマウスデバイスを指す
        devices(0).UsagePage = 1
        devices(0).Usage = 2
        'WM_INPUT を受け取るウィンドウ
        devices(0).Target = Me.Handle
        'WM_INPUT を有効にするデバイス群、devices の数、
        '  RawInputDevice の構造体サイズ
        RegisterRawInputDevices(devices, 1, size)
    End Sub
    
    Private Sub ProcessInputKey(ByRef m As Message)
        Const RidInput As Integer = &H10000003
        Dim headerSize As Integer _
            = Marshal.SizeOf(GetType(RawInputHeader))
        Dim size As Integer _
            = Marshal.SizeOf(GetType(RawInput))
        Dim input As RawInput
        GetRawInputData(m.LParam, RidInput, _
                        input, size, headerSize)
        Dim mouse As RawMouse = input.Mouse
        'デバイスの番号と直前からの移動量を表示
        Me.m_textBox.AppendText( _
            String.Format("{0}({1},{2})" & Environment.NewLine, _
                          input.Header.Device, _
                          mouse.LastX, mouse.LastY))
    End Sub
    Protected Overrides Sub WndProc(ByRef m As Message)
        Const WmInput As Integer = &HFF
        If m.Msg = WmInput Then
            Me.ProcessInputKey(m)
        End If
        MyBase.WndProc(m)
    End Sub 'WndProc

    Private Declare Function RegisterRawInputDevices _
        Lib "user32.dll" ( _
        ByVal devices As RawInputDevice(), _
        ByVal number As Integer, ByVal size As Integer) As Integer
    
    Private Declare Function GetRawInputData Lib "user32.dll" ( _
        ByVal rawInput As IntPtr, ByVal command As Integer, _
        ByRef data As RawInput, ByRef size As Integer, _
        ByVal headerSize As Integer) As Integer

    Private Structure RawInputDevice
        Public UsagePage As Short
        Public Usage As Short
        Public Flags As Integer
        Public Target As IntPtr
    End Structure

    Private Structure RawInputHeader
        Public Type As Integer
        Public Size As Integer
        Public Device As IntPtr
        Public WParam As IntPtr
    End Structure

    Private Structure RawInput
        Public Header As RawInputHeader
        Public Mouse As RawMouse
    End Structure

    Private Structure RawMouse
        Public Flags As Short
        Public ButtonFlags As Short
        Public ButtonData As Short
        Public RawButtons As Integer
        Public LastX As Integer
        Public LastY As Integer
        Public Extra As Integer
    End Structure
End Class
posted by Hongliang at 05:07| Comment(6) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
OSが管理する個々のデバイスインスタンスのハンドルですね>デバイスを識別するハンドル
WMI のデバイス系のクラスに「ハンドル」が入っている場合があるんですが、あれと同じだったりするとまだ救いがありますね。
Posted by 渋木宏明(ひどり) at 2006年05月20日 10:41
なるほどー、と思って Win32_PointingDevice 見に行ったらハンドルがありませんorz
Computer System Hardware Classes の方のクラスなのでそりゃそうだ……です。
まあ、起動時にユーザに確認させればいいと思いますが……いきなりハンドルが再作成されたりしないだろうな。
デバイスID 辺りと相互変換できれば便利ですけどね。
Posted by Hongliang at 2006年05月21日 10:52
例えば USB 接続なら、HID デバイスのとこまで降りてかないとハンドルつかめないかも。
Posted by 渋木宏明(ひどり) at 2006年05月21日 13:13
SP_DEVINFO_DATA 構造体に DevInst : Handle to this device instance なるメンバがあるのでこれ使えば、と思ったらこれ DWORD ですよ。ハンドルって言ったのにっ。
念のために SetupDiEnumDeviceInfo で漁ってみたけど出てくる値は案の定全然違いますし。
……と改めて Raw Input API を眺めてたら GetRawInputDeviceInfo を発見。RIDI_DEVICENAME を使えばデバイスインスタンス ID が手に入りました。GUID 付きで、こっちはデバイスを指す GUID みたいですね。
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\DeviceClasses
にその辺の情報が登録されている模様。
この GUID を使えば SetupDi API でもごにょごにょできるという情報も。
Posted by Hongliang at 2006年05月21日 23:10
複数のマウスを別々に読み取りたく調べていました。
こちらの記事が大変参考になりました。ありがとうございました。
Posted by f at 2010年11月26日 03:35
今【FF14】を最安値で販売しています。
Posted by アラド rmt at 2011年09月10日 15:03
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。