web-dev-qa-db-ja.com

DirectInputアプリケーションでSendInputAPIを使用したキーボードのシミュレーション

カスタムゲームコントローラーアプリケーションのキーボードコマンドをシミュレートしようとしています。 DirectInput環境でコマンドをシミュレートする必要があるため、通常の方法のほとんどは機能しません。フックを使用すると100%動作することはわかっていますが、より簡単な実装を見つけようとしています。

かなりの検索を行ったところ、仮想キーの代わりにスキャンコードでSendInput APIを使用することで機能するはずですが、キーが「くっついている」ように動作するようです。 KEYDOWNイベントとKEYUPイベントの両方を送信しましたが、DirectInput環境でメッセージを送信しようとすると、ゲームはキーが押されているかのように動作します。

たとえば、「W」キーの押下をシミュレートし、そのキーをファーストパーソンシューティングゲームで「前進」アクションにマップした場合、ゲームに参加すると、以下の関数によってキャラクターが前進します。ただし、コマンドを1回発行するだけで、キャラクターが無期限に前進します。

これは、私が呼び出しているSendInput関数のコードスニペット(C#)です。

[DllImport("user32.dll")]
static extern UInt32 SendInput(UInt32 nInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] INPUT[] pInputs, Int32 cbSize);

public static void Test_KeyDown()
{
    INPUT[] InputData = new INPUT[2];
    Key ScanCode = Microsoft.DirectX.DirectInput.Key.W;

    InputData[0].type = 1; //INPUT_KEYBOARD
    InputData[0].wScan = (ushort)ScanCode;
    InputData[0].dwFlags = (uint)SendInputFlags.KEYEVENTF_SCANCODE;

    InputData[1].type = 1; //INPUT_KEYBOARD
    InputData[1].wScan = (ushort)ScanCode;
    InputData[1].dwFlags = (uint)(SendInputFlags.KEYEVENTF_KEYUP | SendInputFlags.KEYEVENTF_UNICODE);

    // send keydown
    if (SendInput(2, InputData, Marshal.SizeOf(InputData[1])) == 0)
    {
        System.Diagnostics.Debug.WriteLine("SendInput failed with code: " +
        Marshal.GetLastWin32Error().ToString());
    }
}

この方法が失われた原因なのか、それとも何か馬鹿げたものがあるのか​​、私にはわかりません。フックを使用する必要がない場合は、コードを複雑にしすぎるのは嫌ですが、これは私にとっても新しい領域です。

誰でもできるどんな助けでも大歓迎です。

ありがとう!

16
Jason

私は自分の問題の解決策を見つけました。それで、私は将来同様の問題を抱えているかもしれない人を助けるためにここに投稿したいと思いました。

スキャンコードを送信するだけの場合、キーアップをスキャンコードフラグとOR演算して(事実上両方のフラグを有効にする)、これがKEYUPとSCANCODEの両方であることをSendInput()APIに通知する必要があるため、keyupコマンドは正しく機能しませんでした。コマンド。

たとえば、次のコードはスキャンコードのキーアップを適切に発行します。

INPUT[] InputData = new INPUT[1];

InputData[0].Type = (UInt32)InputType.KEYBOARD;
//InputData[0].Vk = (ushort)DirectInputKeyScanCode;  //Virtual key is ignored when sending scan code
InputData[0].Scan = (ushort)DirectInputKeyScanCode;
InputData[0].Flags = (uint)KeyboardFlag.KEYUP | (uint)KeyboardFlag.SCANCODE;
InputData[0].Time = 0;
InputData[0].ExtraInfo = IntPtr.Zero;

// Send Keyup flag "OR"ed with Scancode flag for keyup to work properly
SendInput(1, InputData, Marshal.SizeOf(typeof(INPUT)))

応答してくれたハンスに感謝します。元の例が実際に「キー押下」をシミュレートするように、2つのメッセージを調査して連続して送信しましたが、非常に高速です。移動コマンドにはうまく機能しませんが、アクションキーを「タップ」して押したままにしない場合に理想的です。

また、スキャンコードを送信するとき、仮想キーフィールドは無視されます。 MSDNは、この件に関して次のように述べています。

「KEYEVENTF_SCANCODEフラグを設定して、スキャンコードの観点からキーボード入力を定義します。これは、現在使用されているキーボードに関係なく、物理的なキーストロークをシミュレートするのに役立ちます。キーの仮想キー値は、現在のキーボードレイアウトや内容によって変わる場合があります。他のキーが押されましたが、スキャンコードは常に同じです。」

http://msdn.Microsoft.com/en-us/library/ms646271%28v=VS.85%29.aspx

22
Jason

Flashでのプレゼンテーション用にキーボードをシミュレートしようとしましたが、同じ問題が発生していました。メモ帳などのアプリでは機能しましたが、フラッシュでは機能しませんでした。何時間もグーグルした後、私はついにそれを機能させました:

public static void GenerateKey(int vk, bool bExtended)
    {
        INPUT[] inputs = new INPUT[1];
        inputs[0].type = INPUT_KEYBOARD;

        KEYBDINPUT  kb = new KEYBDINPUT(); //{0};
        // generate down 
        if ( bExtended )
            kb.dwFlags  = KEYEVENTF_EXTENDEDKEY;

        kb.wVk  = (ushort)vk;  
        inputs[0].ki = kb;
        SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));

        // generate up 
        //ZeroMemory(&kb, sizeof(KEYBDINPUT));
        //ZeroMemory(&inputs,sizeof(inputs));
        kb.dwFlags  =  KEYEVENTF_KEYUP;
        if ( bExtended )
            kb.dwFlags  |= KEYEVENTF_EXTENDEDKEY;

        kb.wVk    =  (ushort)vk;
        inputs[0].type =  INPUT_KEYBOARD;
        inputs[0].ki  =  kb;
        SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));
    }
3
Mauro