C# 虚拟键修饰键使用发送输入法失败

Posted

技术标签:

【中文标题】C# 虚拟键修饰键使用发送输入法失败【英文标题】:C# virtual key modifier keys fail using Send Input method 【发布时间】:2020-07-27 23:54:00 【问题描述】:

我正在尝试将简单的键序列(ctrl-p、shift-p)发送到前台应用程序(恰好是 Visual Studio 编辑器单元测试缓冲区)。无论我尝试什么,我都无法让我的 control-p 或 shift-p 测试键序列正常工作。 'p' 出现在缓冲区中,但不与控制/移位部分一起出现。

我从 SO 和网络轻松阅读了十几个代码示例,阅读了文档并尝试了太多无法计算的变体。但没有成功。我在标准的 Windows10x64 系统上,将代码作为单元测试运行。

下面的代码同时设置了虚拟键和修饰键的扫描码,这在实践中可能是错误的。我尝试了两种方法都没有成功。我包含了设置这两个字段的代码,以便您可以看到我用于两个字段的代码。

有谁知道我做错了什么?谢谢。

** 更新 ** 我的代码没有失败。我在 Visual Studio 中的单元测试中运行它,它阻止了修改键。当我在单元测试之外运行相同的代码(没有所有调试层等)时,代码运行良好。

教训:不要试图在单元测试中调试你的 SendInput 代码修饰键!!

  [DllImport("user32.dll", SetLastError = true)]
  public static extern uint MapVirtualKey(uint uCode, uint uMapType);

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

  [Flags]
  public enum KeyEventFlag
  
    KeyDown = 0x0000,
    ExtendedKey = 0x0001,
    KeyUp = 0x0002,
    Unicode = 0x0004,
    Scancode = 0x0008
  

  [Flags]
  public enum InputType : int
  
    Mouse = 0,
    Keyboard = 1,
    Hardware = 2
  

  public struct KeyInput
  
    public int type;
    public WinSend.InputUnion u;
  

  [StructLayout(LayoutKind.Explicit)]
  public struct InputUnion
  
    [FieldOffset(0)] public MouseInput mi;
    [FieldOffset(0)] public KeyboardInput ki;
    [FieldOffset(0)] public HardwareInput hi;
  

  [StructLayout(LayoutKind.Sequential)]
  public struct KeyboardInput
  
    public ushort wVk;
    public ushort wScan;
    public uint dwFlags;
    public uint time;
    public IntPtr dwExtraInfo;
  

  const uint MAPVK_VK_TO_VSC = 0x00;
  const uint MAPVK_VSC_TO_VK = 0x01;
  const uint MAPVK_VK_TO_CHAR = 0x02;
  const uint MAPVK_VSC_TO_VK_EX = 0x03;
  const uint MAPVK_VK_TO_VSC_EX = 0x04;

  [TestMethod()]
  public void SendControlKeyTest() 
    DebugOn = true;
    const uint VK_CONTROL = 0x11;
    const uint VK_LCONTROL = 0xA2;
    const uint VK_LSHIFT = 0xA0;

    var keyList = new List<KeyInput>();

    // wVk = A virtual-key code. The code must be a value in the range 1 to 254.
    // If the dwFlags member specifies KEYEVENTF_UNICODE, wVk must be 0.

    // wScan = A hardware scan code for the key. If dwFlags specifies KEYEVENTF_UNICODE,
    // wScan specifies a Unicode char to send to the foreground application.

    // the MapVirtualKey calls return successfully
    var scanControl = MapVirtualKey((uint) VK_CONTROL, (uint) MAPVK_VK_TO_VSC);
    if (scanControl == 0) Dprint("MapVirtualKey for VK_CONTROL failed.p");
    var scanShift = MapVirtualKey((uint) VK_LSHIFT, (uint) MAPVK_VK_TO_VSC);
    if (scanShift == 0) Dprint("MapVirtualKey for VK_LSHIFT failed.");

    // this fails as is, fails with Flag.Unicode, and fails with no XXcode set
    // this also fails with either VK_CONTROL or 0 in the wVk field
    var vkdown = new KeyInput();
    vkdown.type = (int) InputType.Keyboard;
    vkdown.u.ki.wVk = (ushort) VK_CONTROL; // also fails with 0x0
    vkdown.u.ki.dwFlags = (uint) (KeyEventFlag.Scancode | KeyEventFlag.KeyDown);
    vkdown.u.ki.wScan = (ushort) scanControl;
    keyList.Add(vkdown);

    // this works - I see the 'p' when it runs
    var keydown = new KeyInput();
    keydown.type = (int) InputType.Keyboard;
    keydown.u.ki.wVk = 0; // must be zero if Flag.Unicode is set
    keydown.u.ki.dwFlags = (uint) (KeyEventFlag.Unicode | KeyEventFlag.KeyDown);
    keydown.u.ki.wScan = (ushort) 'p';
    keyList.Add(keydown);

    // this works - I see the 'p' when it runs
    var keyup = new KeyInput();
    keyup.type = (int) InputType.Keyboard;
    keyup.u.ki.wVk = 0; // must be zero if Flag.Unicode is set
    keyup.u.ki.dwFlags = (uint) (KeyEventFlag.Unicode | KeyEventFlag.KeyUp);
    keyup.u.ki.wScan = (ushort) 'p';
    keyList.Add(keyup);

    // this fails as is, fails with Flag.Unicode, and fails with no XXcode set
    // this also fails with either VK_CONTROL or 0 in the wVk field
    var vkup = new KeyInput();
    vkup.type = (int) InputType.Keyboard;
    vkup.u.ki.wVk = (ushort) VK_CONTROL; // also fails with 0x0
    vkup.u.ki.dwFlags = (uint) (KeyEventFlag.Scancode| KeyEventFlag.KeyUp);
    vkup.u.ki.wScan = (ushort) scanControl;
    keyList.Add(vkup);

    // SendInput returns 4, which means it sent 4 events successfully
    var keycount = SendInput((uint) keyList.Count, keyList.ToArray(),
      Marshal.SizeOf(typeof(KeyInput)));
    Dprint($"Sent keycount keys to the system.");

    // The Control-P should move my caret up one line, but it does not.
    // Shift should create a capital P in the buffer, but it does not.
    // All I see is a 'p' in the Visual studio buffer
  

【问题讨论】:

【参考方案1】:

很抱歉间接回答,但这是针对 WPF 应用程序的 100% 有效且经过测试的解决方案。

internal static class NativeMethods

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

    [Flags]
    public enum KeyboardFlags : uint
    
        None = 0,

        /// <summary>
        /// KEYEVENTF_EXTENDEDKEY = 0x0001 (If specified, the scan code was preceded by a prefix byte that has the value 0xE0 (224).)
        /// </summary>
        ExtendedKey = 1,

        /// <summary>
        /// KEYEVENTF_KEYUP = 0x0002 (If specified, the key is being released. If not specified, the key is being pressed.)
        /// </summary>
        KeyUp = 2,

        /// <summary>
        /// KEYEVENTF_UNICODE = 0x0004 (If specified, wScan identifies the key and wVk is ignored.)
        /// </summary>
        Unicode = 4,

        /// <summary>
        /// KEYEVENTF_SCANCODE = 0x0008 (Windows 2000/XP: If specified, the system synthesizes a VK_PACKET keystroke. The wVk parameter must be zero. This flag can only be combined with the KEYEVENTF_KEYUP flag. For more information, see the Remarks section.)
        /// </summary>
        ScanCode = 8,
    

    [Flags]
    public enum MouseFlags : uint
    
        /// <summary>
        /// Specifies that movement occurred.
        /// </summary>
        Move = 0x0001,

        /// <summary>
        /// Specifies that the left button was pressed.
        /// </summary>
        LeftDown = 0x0002,

        /// <summary>
        /// Specifies that the left button was released.
        /// </summary>
        LeftUp = 0x0004,

        /// <summary>
        /// Specifies that the right button was pressed.
        /// </summary>
        RightDown = 0x0008,

        /// <summary>
        /// Specifies that the right button was released.
        /// </summary>
        RightUp = 0x0010,

        /// <summary>
        /// Specifies that the middle button was pressed.
        /// </summary>
        MiddleDown = 0x0020,

        /// <summary>
        /// Specifies that the middle button was released.
        /// </summary>
        MiddleUp = 0x0040,

        /// <summary>
        /// Windows 2000/XP: Specifies that an X button was pressed.
        /// </summary>
        XDown = 0x0080,

        /// <summary>
        /// Windows 2000/XP: Specifies that an X button was released.
        /// </summary>
        XUp = 0x0100,

        /// <summary>
        /// Windows NT/2000/XP: Specifies that the wheel was moved, if the mouse has a wheel. The amount of movement is specified in mouseData. 
        /// </summary>
        VerticalWheel = 0x0800,

        /// <summary>
        /// Specifies that the wheel was moved horizontally, if the mouse has a wheel. The amount of movement is specified in mouseData. Windows 2000/XP:  Not supported.
        /// </summary>
        HorizontalWheel = 0x1000,

        /// <summary>
        /// Windows 2000/XP: Maps coordinates to the entire desktop. Must be used with MOUSEEVENTF_ABSOLUTE.
        /// </summary>
        VirtualDesk = 0x4000,

        /// <summary>
        /// Specifies that the dx and dy members contain normalized absolute coordinates. If the flag is not set, dxand dy contain relative data (the change in position since the last reported position). This flag can be set, or not set, regardless of what kind of mouse or other pointing device, if any, is connected to the system. For further information about relative mouse motion, see the following Remarks section.
        /// </summary>
        Absolute = 0x8000,
    

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT
    
        public ushort virtualKey;
        public ushort scanCode;
        public KeyboardFlags flags;
        public uint timeStamp;
        public IntPtr extraInfo;
    

    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSEINPUT
    
        public int deltaX;
        public int deltaY;
        public uint mouseData;
        public MouseFlags flags;
        public uint time;
        public IntPtr extraInfo;
    

    [StructLayout(LayoutKind.Sequential)]
    public struct HARDWAREINPUT
    
        public uint message;
        public ushort wParamL;
        public ushort wParamH;
    

    [StructLayout(LayoutKind.Explicit)]
    public struct InputUnion
    
        [FieldOffset(0)]
        public MOUSEINPUT mouse;
        [FieldOffset(0)]
        public KEYBDINPUT keyboard;
        [FieldOffset(0)]
        public HARDWAREINPUT hardware;
    
    public enum InputType : int
    
        Mouse = 0,
        Keyboard = 1,
        Hardware = 2
    
    public struct INPUT
    
        public InputType type;
        public InputUnion union;
    

KeyInterop.VirtualKeyFromKey 这是 .NET 类的标准方法。我猜可以换成WinAPIMapVirtualKey

public static void ModifiedKeyStroke(Key key, ModifierKeys modifiers)

    static NativeMethods.INPUT BuildINPUT(Key k, NativeMethods.KeyboardFlags flags) => new NativeMethods.INPUT
    
        type = NativeMethods.InputType.Keyboard,
        union = new NativeMethods.InputUnion  keyboard = new NativeMethods.KEYBDINPUT  virtualKey = (ushort)KeyInterop.VirtualKeyFromKey(k), scanCode = 0, flags = flags, timeStamp = 0, extraInfo = IntPtr.Zero  
    ;
    List<Key> keys = new List<Key>();
    if (modifiers.HasFlag(ModifierKeys.Control)) keys.Add(Key.LeftCtrl);
    if (modifiers.HasFlag(ModifierKeys.Alt)) keys.Add(Key.LeftAlt);
    if (modifiers.HasFlag(ModifierKeys.Shift)) keys.Add(Key.LeftShift);
    if (modifiers.HasFlag(ModifierKeys.Windows)) keys.Add(Key.LWin);
    keys.Add(key);
    NativeMethods.INPUT[] inputs = new NativeMethods.INPUT[keys.Count * 2];
    uint i = 0;
    foreach (Key k in keys) inputs[i++] = BuildINPUT(k, NativeMethods.KeyboardFlags.None);
    keys.Reverse();
    foreach (Key k in keys) inputs[i++] = BuildINPUT(k, NativeMethods.KeyboardFlags.KeyUp);
    _ = NativeMethods.SendInput(i, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));

用法

ModifiedKeyStroke(Key.C, ModifierKeys.Control); // sends Ctrl+C

【讨论】:

您好,感谢您发布解决方案。不幸的是,我没有使用 WPF,也没有使用 Windows Forms(?) 来获取 Key.* 定义。我希望找出我的代码做错了什么,即使它是基于阅读其他十几段工作代码。 @Kevin KeyModifierKeys 很简单enums,您可以从 WPF 元数据中获取它(将其作为文本复制到代码中)。 @Kevin 如果您觉得它有趣或有帮助,我建议备份答案中的代码。我想尽快删除这个被否决且未被接受的答案。 @Kevin 这里是KeyInterop.VirtualKeyFromKey 感谢您的帮助。我复制了您的代码,因此您可以删除答案。我不确定他们为什么对试图提供帮助的人投反对票。我将您的 WPF KeyInterop 键与我自己的键进行了比较,它们的顺序相同并且具有相同的值。我开始怀疑 VStudio 单元测试缓冲区(或调试器层)是否会阻止修改键工作。其他键(例如左箭头)可以正常工作。只是转移和控制不起作用。我想我在单元测试缓冲区中看到了一个 alt-x 案例,因为它触发了 VS 上的菜单。再次感谢您。

以上是关于C# 虚拟键修饰键使用发送输入法失败的主要内容,如果未能解决你的问题,请参考以下文章

C# MouseKeyHook:某些键 + 修饰符组合不起作用

在 C# 中模拟键盘输入

如何使用 c# 发送(keybd_event)Unicode 键

控制台应用程序的 C# 箭头键输入

在 Java 中使用组合键

禁用alt键临时c#