C#コンソールアプリでSendInputを扱いたい


SendInputが動作せず悩んでいます。  
環境はVisualStudio2022、C#コンソールアプリ、.Net7.0です。

主に以下のＨＰを参考に他、C#でSendInputを扱ったＨＰを見ながら作成しました。

<https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-
sendinput>

<https://social.msdn.microsoft.com/Forums/ja-
JP/f1df67f9-33a2-4293-bfe3-46d2810846f3/cwin32apinotepadpostmessagecontrol-a?forum=windowsgeneraldevelopmentissuesja>

エラーコードからパラメータの指定が間違っているのではないかと思いますが行き詰まっています。  
アドバイスよろしくお願いいたします。

※メモ帳にctrl+vを送信しているため、動作にはメモ帳を起動し何かctrl+cをしておく必要があります。

[code]

    using System.Runtime.InteropServices;
    
    internal class Program
    {
        #pragma warning disable IDE0051
        private const int INPUT_MOUSE = 0;
        private const int INPUT_KEYBOARD = 1;
        private const int INPUT_HARDWARE = 2;
    
        private const int MOUSEEVENTF_MOVE = 0x1;
        private const int MOUSEEVENTF_ABSOLUTE = 0x8000;
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;
        private const int MOUSEEVENTF_RIGHTDOWN = 0x8;
        private const int MOUSEEVENTF_RIGHTUP = 0x10;
        private const int MOUSEEVENTF_MIDDLEDOWN = 0x20;
        private const int MOUSEEVENTF_MIDDLEUP = 0x40;
        private const int MOUSEEVENTF_WHEEL = 0x800;
        private const int WHEEL_DELTA = 120;
    
        private const short VK_CONTROL = 0x11;
        private const short VK_V = (short)'V';
    
        private const int KEYEVENTF_KEYDOWN = 0x0;
        private const int KEYEVENTF_KEYUP = 0x2;
        private const int KEYEVENTF_EXTENDEDKEY = 0x1;
        #pragma warning restore IDE0051
    
        [StructLayout(LayoutKind.Sequential)]
        private struct MOUSEINPUT
        {
            public int dx;
            public int dy;
            public int mouseData;
            public int dwFlags;
            public int time;
            public int dwExtraInfo;
        };
    
        [StructLayout(LayoutKind.Sequential)]
        private struct KEYBDINPUT
        {
            public short wVk;
            public short wScan;
            public int dwFlags;
            public int time;
            public int dwExtraInfo;
        };
    
        [StructLayout(LayoutKind.Sequential)]
        private struct HARDWAREINPUT
        {
            public int uMsg;
            public short wParamL;
            public short wParamH;
        };
    
        [StructLayout(LayoutKind.Explicit)]
        private struct INPUT
        {
            [FieldOffset(0)] public int type;
            [FieldOffset(4)] public MOUSEINPUT mi;
            [FieldOffset(4)] public KEYBDINPUT ki;
            [FieldOffset(4)] public HARDWAREINPUT hi;
        };
    
        private static class NativeMethods
        {
            #pragma warning disable SYSLIB1054
            [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr FindWindow(string lpClassName, string? lpWindowName);
    
            [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildafter, string lpszClass, string? lpszWindow);
    
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetForegroundWindow(IntPtr hWnd);
    
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetFocus(IntPtr hWnd);
    
            [DllImport("user32.dll", EntryPoint = "MapVirtualKeyA", SetLastError = true)]
            public static extern int MapVirtualKey(int wCode, int wMapType);
    
            [DllImport("user32.dll", SetLastError = true)]
            public static extern uint SendInput(int nInputs, ref INPUT[] pInputs, int cbsize);
            #pragma warning restore SYSLIB1054
        }
    
        private static void Main()
        {
            int i = 0;
            uint rtnCode;
            int errCode;
    
            IntPtr hWnd1, hWnd2;
            hWnd1 = NativeMethods.FindWindow("Notepad", null);
            hWnd2 = NativeMethods.FindWindowEx(hWnd1, IntPtr.Zero, "Edit", null);
            NativeMethods.SetForegroundWindow(hWnd1);
            NativeMethods.SetFocus(hWnd2);
    
            INPUT defaultInput = new();
            INPUT[] inputs = new INPUT[4];
    
            defaultInput.type = INPUT_KEYBOARD;
            defaultInput.ki.wVk = 0x0;
            defaultInput.ki.wScan = 0; // (short)NativeMethods.MapVirtualKey(defaultInput.ki.wVk, 0);
            defaultInput.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
            defaultInput.ki.dwExtraInfo = 0;
            defaultInput.ki.time = 0;
    
            inputs[0] = defaultInput;
            inputs[0].ki.wVk = VK_CONTROL;
            inputs[0].ki.dwFlags = KEYEVENTF_KEYDOWN;
    
            inputs[1] = defaultInput;
            inputs[1].ki.wVk = VK_V;
            inputs[1].ki.dwFlags = KEYEVENTF_KEYDOWN;
    
            inputs[2] = defaultInput;
            inputs[2].ki.wVk = VK_V;
            inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;
    
            inputs[3] = defaultInput;
            inputs[3].ki.wVk = VK_CONTROL;
            inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;
    
            while (true)
            {
                Thread.Sleep(2000);
                rtnCode = NativeMethods.SendInput(4, ref inputs, Marshal.SizeOf(inputs[0]));
                errCode = Marshal.GetLastWin32Error();
                Console.WriteLine("{0} : rtn({1}) / err({2})", ++i, rtnCode, errCode);
            }
        }
    }
    
[/code]




まとめておきます。

(1) SendInput の第２引数

[DllImport("user32.dll", SetLastError = true)]  
public static extern uint SendInput(int nInputs, INPUT[] pInputs, int cbsize);

udaken さんのご指摘にあるように、第２引数は ref を外してください。

(2) INPUT 構造体

[code]

    typedef struct tagINPUT {
      DWORD type;
      union {
        MOUSEINPUT    mi;
        KEYBDINPUT    ki;
        HARDWAREINPUT hi;
      } DUMMYUNIONNAME;
    } INPUT, *PINPUT, *LPINPUT
    
[/code]

32 ビットだと、type のすぐあとに MOUSEINPUT や KEYBDINPUT 等が配置されますが、  
64 ビットだと 64ビット境界に配置（アライメント）されるため、空き領域ができます。  
aroma さんのソースでは、FieldOffset(4) → FieldOffset(8) となります。

.NET のソースでは

[code]

    [StructLayout(LayoutKind.Sequential)]
    public struct INPUT
    {
        public int type;
        public INPUTUNION inputUnion;
    }
    
    [StructLayout(LayoutKind.Explicit)]
    public struct INPUTUNION
    {
        [FieldOffset(0)]
        public MOUSEINPUT mi;
        [FieldOffset(0)]
        public KEYBDINPUT ki;
        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }
    
[/code]

のようにアライメントを考慮した記述になっています。




まとめておきます。

(1) SendInput の第２引数

[DllImport("user32.dll", SetLastError = true)]  
public static extern uint SendInput(int nInputs, INPUT[] pInputs, int cbsize);

udaken さんのご指摘にあるように、第２引数は ref を外してください。

(2) INPUT 構造体

[code]

    typedef struct tagINPUT {
      DWORD type;
      union {
        MOUSEINPUT    mi;
        KEYBDINPUT    ki;
        HARDWAREINPUT hi;
      } DUMMYUNIONNAME;
    } INPUT, *PINPUT, *LPINPUT
    
[/code]

32 ビットだと、type のすぐあとに MOUSEINPUT や KEYBDINPUT 等が配置されますが、  
64 ビットだと 64ビット境界に配置（アライメント）されるため、空き領域ができます。  
aroma さんのソースでは、FieldOffset(4) → FieldOffset(8) となります。

.NET のソースでは

[code]

    [StructLayout(LayoutKind.Sequential)]
    public struct INPUT
    {
        public int type;
        public INPUTUNION inputUnion;
    }
    
    [StructLayout(LayoutKind.Explicit)]
    public struct INPUTUNION
    {
        [FieldOffset(0)]
        public MOUSEINPUT mi;
        [FieldOffset(0)]
        public KEYBDINPUT ki;
        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }
    
[/code]

のようにアライメントを考慮した記述になっています。

