文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)する

# はじめに

(マルチバイト)文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)するサンプルコード。

任意の他のウィンドウにフォーカスを移せば、アプリケーションを越えて(別プロセスの)ウィンドウへキー入力が可能。自・他(任意)のアプリケーションに対してキーボード入力を送信できます。これにより、自・他(任意)のアプリケーションの自動化やリモート操作等が可能です。

キーワード: キー操作シミュレート, 仮想キーコード送信, Win32 API(user32.dll内) SendInput

# 処理方法

主要部分は、
* Win32 API(user32.dll内)のSendInput関数を呼び出して、任意のキー入力を送信
* Input構造体の中のScanCode、または、VirtualKeyで送信するキー内容を指定: 
  * 文字列を送信する場合は、ScanCodeに(マルチバイト)文字コードを代入し、
  * 仮想キーコードを送信する場合は、VirtualKeyに仮想キーコードを代入 (仮想キーコードは特殊キーも含めて細かく指定可能)
* 主要機能は自作関数InputSender.KeyPressとして実装

# 実装＋テストコード

テストコードのGUIは:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/cadeb9f0-ff5d-1ab4-72df-b47224724af0.png)

```C#
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        // sec: test code

        // ボタンをクリックしてテスト開始
        private void button1_Click(object sender, EventArgs e) {
            Test_KeyPress_Basic(textBox1);
            Test_KeyPress_ToOtherApp();
        }

        // キー入力の送信テスト
        internal static void Test_KeyPress_Basic(TextBox textBox) {
            // 結果: TextBoxへの入力OK: abcあいう醤油★☺<改行>ちｚつ
            // ・ScanCodeの指定時は、全角モード時でも"abc"と入力が入る。
            // ・仮想キー指定時は、全角モード時で、Aと送ると「ち」と入力が入る。Enterを送ると、変換が確定となる。
            // ・全角/半角キーを送ると、全角/半角が切り替わる。

            Console.WriteLine("▼全角/半角モードでコードからキー入力を送る");
            textBox.Focus();
            textBox.ImeMode = ImeMode.Hiragana; // 全角モードに強制

            // ScanCodeの指定時
            InputSender.KeyPress("abc"); // ScanCodeの指定時は、全角モード時でも"abc"と入力が入る。
            InputSender.KeyPress("あいう醤油★☺\n"); // 全角文字を直接送信可

            // 仮想キー指定時
            InputSender.KeyPress(new[] { InputSender.VK_A, InputSender.VK_RETURN }); // 仮想キー指定時は、全角モード時で、Aと送ると「ち」と入力が入る。Enterを送ると、変換が確定となる。
            InputSender.KeyPress(new[] { InputSender.VK_KANA, InputSender.VK_Z, InputSender.VK_RETURN }); // 全角/半角キーを送ると、全角/半角が切り替わる。
            InputSender.KeyPress(new[] { InputSender.VK_KANA, InputSender.VK_Z, InputSender.VK_RETURN });
        }

        // 他アプリへのキー入力の送信テスト
        internal static async void Test_KeyPress_ToOtherApp() {
            // 結果: 他アプリへの入力OK: abc0ちわち
            // ・他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力が可能。

            Console.WriteLine("▼他のアプリ(別プロセス)にキー入力を送る");
            Console.WriteLine("他のアプリにフォーカスを移して、全角モードで待機して下さい。");
            await System.Threading.Tasks.Task.Delay(3000);

            InputSender.KeyPress("abc"); // 他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力が可能。
            InputSender.KeyPress(new[] { InputSender.VK_NUMPAD0, InputSender.VK_A });
            InputSender.KeyPress(new[] { InputSender.VK_0, InputSender.VK_A });
        }
    }

    // sec: キー入力を送信

    // (マルチバイト)文字列、または、仮想キーコードを送信して、キー操作をシミュレート(擬似的に操作)
    public static class InputSender {
        // 他のアプリにフォーカスを移せば、アプリを越えて(別プロセスの)ウィンドウへキー入力可能

        // (マルチバイト)文字列のキー入力を送信 (ScanCodeの指定となる)
        public static void KeyPress(string text) {

            Input[] inputs = new Input[2 * text.Length];
            for (var i_char = 0; i_char < text.Length; i_char++) {

                var val_sc = (short)text[i_char];
                var i_in = 2 * i_char;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.ScanCode = val_sc;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_UNICODE | KEYEVENTF_KEYDOWN;

                i_in = 2 * i_char + 1;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.ScanCode = val_sc;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
            }
            InputSender.SendInput(inputs.Length, inputs, Marshal.SizeOf<Input>());
        }
        public static void KeyPress(char text) => InputSender.KeyPress(text.ToString());

        // 仮想キーコードを送信 (VirtualKeyの指定となる)
        public static void KeyPress(short[] vkeys) {

            var inputs = new Input[2 * vkeys.Length];
            for (var i_vkey = 0; i_vkey < vkeys.Length; i_vkey++) {

                var val_vk = vkeys[i_vkey];
                var i_in = 2 * i_vkey;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.VirtualKey = val_vk;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_KEYDOWN;

                i_in = 2 * i_vkey + 1;
                inputs[i_in] = new Input();
                inputs[i_in].Type = INPUT_KEYBOARD;
                inputs[i_in].ui.Keyboard.VirtualKey = val_vk;
                inputs[i_in].ui.Keyboard.Flags = KEYEVENTF_KEYUP;
            }
            InputSender.SendInput(inputs.Length, inputs, Marshal.SizeOf<Input>());
        }
        public static void KeyPress(short vkey) => InputSender.KeyPress(new[] { vkey });

        // sec: BeginInvoke

        // [不要?] このWinアプリのスレッド上で呼出しを実施
        //public static void BeginInvoke(this Control control, Action func) => control.BeginInvoke(func);
        // 使用方法: this.BeginInvoke(() => { SendInput.KeyPress("abc"); }); 等

        // sec: 仮想キー値

        // ref: 仮想キーの状態
        // http://wisdom.sakura.ne.jp/system/winapi/win32/win32.html

        // 必要な分を下記に適宜もっと追加
        public const short VK_A = 0x41;
        public const short VK_Z = 0x5A;
        public const short VK_0 = 0x30;
        public const short VK_9 = 0x39;
        public const short VK_RETURN = 0x0D; // Enter
        public const short VK_SHIFT = 0x10; // SHIFTキー
        public const short VK_KANA = 0x15;
        public const short VK_ESCAPE = 0x1B;
        public const short VK_SPACE = 0x20;

        public const short VK_NUMPAD0 = 0x60; // テンキー
        public const short VK_NUMPAD9 = 0x69;
        public const short VK_MULTIPLY = 0x6A; // テンキー *
        public const short VK_ADD = 0x6B; // テンキー +
        public const short VK_SEPARATOR = 0x6C;
        public const short VK_SUBTRACT = 0x6D; // テンキー -
        public const short VK_DECIMAL = 0x6E; // テンキー .
        public const short VK_DIVIDE = 0x6F; // テンキー / 


        // sec: Win API

        // ref: C# - SendInputでマルチバイト文字列を送信する
        // https://qiita.com/kob58im/items/60abf22e6ce9a3a0ed26

        // キー操作、マウス操作をシミュレート(擬似的に操作する)
        [DllImport("user32.dll", SetLastError = true)]
        public extern static void SendInput(int nInputs, Input[] pInputs, int cbsize);

        //[DllImport("user32.dll", SetLastError = true)]
        //public extern static IntPtr GetMessageExtraInfo();

        public const int INPUT_MOUSE = 0;    // マウスイベント
        public const int INPUT_KEYBOARD = 1; // キーボードイベント
        public const int INPUT_HARDWARE = 2; // ハードウェアイベント

        public const int MOUSEEVENTF_MOVE = 0x1;        // マウスを移動する
        public const int MOUSEEVENTF_ABSOLUTE = 0x8000; // 絶対座標指定
        public const int MOUSEEVENTF_LEFTDOWN = 0x2;    // 左　ボタンを押す
        public const int MOUSEEVENTF_LEFTUP = 0x4;      // 左　ボタンを離す
        public const int MOUSEEVENTF_RIGHTDOWN = 0x8;   // 右　ボタンを押す
        public const int MOUSEEVENTF_RIGHTUP = 0x10;    // 右　ボタンを離す
        public const int MOUSEEVENTF_MIDDLEDOWN = 0x20; // 中央ボタンを押す
        public const int MOUSEEVENTF_MIDDLEUP = 0x40;   // 中央ボタンを離す
        public const int MOUSEEVENTF_WHEEL = 0x800;     // ホイールを回転する
        public const int WHEEL_DELTA = 120;             // ホイール回転値

        public const int KEYEVENTF_EXTENDEDKEY = 0x0001; // 拡張コード
        public const int KEYEVENTF_KEYDOWN = 0x0000; // キーを押す
        public const int KEYEVENTF_KEYUP = 0x0002; // キーを離す
        public const int KEYEVENTF_SCANCODE = 0x0008;
        public const int KEYEVENTF_UNICODE = 0x0004;

        // ▼case: 変換する場合
        // 仮想キーコードをスキャンコードに変換
        //[DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
        //private extern static int MapVirtualKey(
        //    int wCode, int wMapType);

        // public const int MAPVK_VK_TO_VSC = 0;
        // public const int MAPVK_VSC_TO_VK = 1;
        // ▲case: end

        [StructLayout(LayoutKind.Sequential)]
        public struct MouseInput {
            public int X;
            public int Y;
            public int Data;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KeyboardInput {
            public short VirtualKey;
            public short ScanCode;
            public int Flags;
            public int Time;
            public IntPtr ExtraInfo;
        }
        // VirtualKey・ScanCodeは、winuser.hのKEYBDINPUT構造体では、WORD(16ビット符号なし整数、unsigned short型/2バイト)の定義＝ushort
        // https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-keybdinput
        // https://learn.microsoft.com/ja-jp/windows/win32/winprog/windows-data-types#word

        [StructLayout(LayoutKind.Sequential)]
        public struct HardwareInput {
            public int uMsg;
            public short wParamL;
            public short wParamH;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct Input {
            public int Type;
            public InputUnion ui;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct InputUnion {
            [FieldOffset(0)]
            public MouseInput Mouse;
            [FieldOffset(0)]
            public KeyboardInput Keyboard;
            [FieldOffset(0)]
            public HardwareInput Hardware;
        }
    }
}
```