LoginSignup
7
6

C# SendInputによるマウス操作の落とし穴 - 64bitの罠

Last updated at Posted at 2019-10-26

Windows10 64bit環境にて。

今回の結論 - 参考サイト

32bit/64bitでPackサイズ(アライメント)が変わるので、アンマネージのunionstructLayoutKind.Explicit(0以外)で配置するとはまるケースがあるよう。今回はまさにこれ。
https://jinblog.at.webry.info/201604/article_1.html

現象

ペイント(mspaint)上でイベントを受け取れているかを確認。

csc /platform x86 xxx.cs
だとSendInputが効いているが、

csc xxx.cs
もしくは
csc /platform x64 xxx.cs
では動作しない。(SetCursorPosによる移動しかしない)

SendInputでのマウス操作の落とし穴 - その他

  • カーソルが動いておらず、受け取れないアプリがある(Scrcpyとか)。(SetCursorPosで対策済)
  • 構造体の型定義が正しくない。今回これ
  • ExtraInfoを0(IntPtr.Zero)のままにしている(解消済)(ただ、0でも動くかも)
  • フォーカスが移っていないとイベントを受け取れない?(マウスイベントは事前にフォーカス移さなくても取れるっぽい)
  • 座標系を(0,0)-(65535,65535)系に変換していない。
  • マルチディスプレイで座標が崩れる。(未確認)

ソースコード

64bitでは動かない →修正済み


using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;

public static class MouseInputWrapper
{
    class NativeMethods
    {
        [DllImport("user32.dll", SetLastError = true)]
        public extern static int SendInput(int nInputs, ref Input pInputs, int cbsize);

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

        [DllImport("user32.dll")]
        public extern static bool SetCursorPos(int x, int y);
    }

    const int MOUSEEVENTF_MOVE        = 0x0001;
    const int MOUSEEVENTF_LEFTDOWN    = 0x0002;
    const int MOUSEEVENTF_LEFTUP      = 0x0004;
    const int MOUSEEVENTF_VIRTUALDESK = 0x4000;
    const int MOUSEEVENTF_ABSOLUTE    = 0x8000;

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

    [StructLayout(LayoutKind.Sequential)]
    struct KeyboardInput
    {
        public short VirtualKey;
        public short ScanCode;
        public int Flags;
        public int Time;
        public IntPtr ExtraInfo;
    }

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

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

    [StructLayout(LayoutKind.Explicit)]
    struct InputUnion
    {
        [FieldOffset(0)]
        public MouseInput Mouse;
        [FieldOffset(0)]
        public KeyboardInput Keyboard;
        [FieldOffset(0)]
        public HardwareInput Hardware;
    }
/*
    // 64 bitで動かないコード
    [StructLayout(LayoutKind.Explicit)]
    struct Input
    {
        [FieldOffset(0)]
        public int Type; // 0:Mouse,  1:Keyboard,  2:Hardware
        [FieldOffset(4)] // ここがpackingのアライメントが64bitだと8にしないといけない
        public MouseInput Mouse;
        [FieldOffset(4)]
        public KeyboardInput Keyboard;
        [FieldOffset(4)]
        public HardwareInput Hardware;
    }
*/

    static Input MouseMoveData(int x, int y, System.Windows.Forms.Screen screen, IntPtr extraInfo)
    {
        x = x * 65535 / screen.Bounds.Width;
        y = y * 65535 / screen.Bounds.Height;

        Input input = new Input();
        input.Type = 0; // MOUSE = 0
        input.ui.Mouse.Flags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // | MOUSEEVENTF_VIRTUALDESK
        input.ui.Mouse.Data = 0;
        input.ui.Mouse.X = x;
        input.ui.Mouse.Y = y;
        input.ui.Mouse.Time = 0;
        input.ui.Mouse.ExtraInfo = extraInfo;
        return input;
    }

    static Input MouseDataWithoutMove(int flags, IntPtr extraInfo)
    {
        Input input = new Input();
        input.Type = 0; // MOUSE = 0
        input.ui.Mouse.Flags = flags;
        input.ui.Mouse.Data = 0;
        input.ui.Mouse.X = 0;
        input.ui.Mouse.Y = 0;
        input.ui.Mouse.Time = 0;
        input.ui.Mouse.ExtraInfo = extraInfo;
        return input;
    }



    public static void SendMouseMove(int x, int y, System.Windows.Forms.Screen screen)
    {
        IntPtr extraInfo = NativeMethods.GetMessageExtraInfo();
        Input inputs = MouseMoveData(x,y,screen,extraInfo);
        NativeMethods.SetCursorPos(x,y);
        int ret = NativeMethods.SendInput(1, ref inputs, Marshal.SizeOf(inputs));
//        int errCode = Marshal.GetLastWin32Error();
        Console.WriteLine(ret);
//        Console.WriteLine(errCode);
    }

    public static void SendMouseDown()
    {
        IntPtr extraInfo = NativeMethods.GetMessageExtraInfo();
        Input inputs = MouseDataWithoutMove(MOUSEEVENTF_LEFTDOWN,extraInfo);
        int ret = NativeMethods.SendInput(1, ref inputs, Marshal.SizeOf(inputs));
    }
    public static void SendMouseUp()
    {
        IntPtr extraInfo = NativeMethods.GetMessageExtraInfo();
        Input inputs = MouseDataWithoutMove(MOUSEEVENTF_LEFTUP,extraInfo);
        int ret = NativeMethods.SendInput(1, ref inputs, Marshal.SizeOf(inputs));
    }
}



class Test:Form
{
    Test()
    {
        ClientSize = new Size(220,80);

        var btn = new Button();
        btn.Text = "MoveAndClick";
        btn.Width = 150;
        btn.Click += (sender,e)=>{
            Task task = Task.Run(() =>
            {
                Thread.Sleep(2000);
                MouseInputWrapper.SendMouseMove(150,150,System.Windows.Forms.Screen.PrimaryScreen);
                Thread.Sleep(200);
                MouseInputWrapper.SendMouseDown();
                Thread.Sleep(200);
                MouseInputWrapper.SendMouseMove(200,150,System.Windows.Forms.Screen.PrimaryScreen);
                Thread.Sleep(200);
                MouseInputWrapper.SendMouseUp();
            });
        };
        Controls.Add(btn);
    }

    [STAThread]
    static void Main()
    {
        Application.Run(new Test());
    }
}

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6