C#模拟键盘操作:从SendKeys到SendInput的完整实现与实战
2026/7/31 16:41:01 网站建设 项目流程

1. 项目概述:为什么我们需要模拟键盘操作?

在自动化测试、游戏辅助、办公效率工具乃至远程控制等场景中,程序能够模拟人类的键盘输入,是一个既基础又强大的能力。想象一下,你需要定期向一个不支持批量导入的Web表单中录入数百条数据,或者需要让一个游戏角色在特定时间执行一连串复杂的技能组合,又或者是在演示软件时自动播放预设的流程。手动操作不仅枯燥低效,还容易出错。这时,让C#程序来“扮演”你的手指,精准、不知疲倦地敲击键盘,价值就凸显出来了。

“C#实现模拟键盘操作”这个项目,核心就是探讨如何在Windows平台上,使用C#语言调用系统级API,向操作系统发送虚拟的键盘事件,从而控制任何获得焦点的应用程序,实现自动化输入。这不仅仅是调用一两个SendKeys.Send()那么简单,真正的挑战在于如何模拟所有按键(包括功能键、组合键)、如何控制按键的按下与抬起时序、如何应对有安全限制的程序(如某些游戏的反作弊系统),以及如何写出稳定、可复用的代码。网上能找到的代码片段往往只解决了皮毛,缺乏对底层原理的深入解释和健壮性处理,这正是我们这篇分享要填补的空白。

我将从一个有十多年C#开发经验的视角,带你从原理到实践,拆解几种主流实现方案,并附上我精心打磨、可直接用于生产环境的完整工具类源码。无论你是想快速实现一个自动化脚本的新手,还是需要深入理解Windows消息机制的中高级开发者,这篇文章都能给你带来实实在在的收获。

2. 核心原理与方案选型:Windows下的键盘事件是如何工作的?

在动手写代码之前,我们必须先搞清楚:当你在物理键盘上按下一个键,到屏幕上出现字符,这中间发生了什么?对于程序模拟来说,我们需要在哪个环节“介入”?

简单来说,Windows操作系统使用消息循环(Message Loop)机制来处理输入。当你按下键盘,键盘驱动程序会生成一个扫描码(Scan Code),系统将其包装成一个WM_KEYDOWNWM_SYSKEYDOWN消息,放入当前拥有焦点的窗口的消息队列中。窗口过程处理这个消息,可能会触发字符消息WM_CHAR,最终完成输入。松开按键时,则会生成WM_KEYUPWM_SYSKEYUP消息。

因此,模拟键盘操作的本质,就是由我们的程序主动创建并发送这些WM_KEYDOWNWM_KEYUP消息到目标窗口。在C#中,我们主要通过以下几种方式实现:

2.1 方案一:System.Windows.Forms.SendKeys(最简方案)

这是.NET Framework/WinForms自带的最简单的模拟方法。

using System.Windows.Forms; // 发送字符串 SendKeys.Send("Hello World"); // 发送特殊键和组合键 SendKeys.Send("{ENTER}"); SendKeys.Send("^s"); // Ctrl+S

优点:使用极其简单,无需引入复杂API。致命缺点

  1. 同步阻塞SendKeys.Send是同步的,它会等待目标应用程序处理完当前消息后才返回。如果目标程序“卡住”了,你的程序也会被阻塞。
  2. 焦点依赖极强:它只能发送给当前拥有焦点的窗口。在发送前,你必须确保目标窗口是激活状态,这本身在自动化流程中就是一个不稳定因素。
  3. 功能有限:对某些特殊键(如多媒体键)或复杂的按键时序控制支持不好。

实操心得SendKeys.Send只适用于非常简单的、对可靠性要求不高的桌面自动化场景,比如在你自己开发的一个测试工具里,操作另一个你自己也完全控制的程序。对于任何严肃的自动化任务,我都不推荐使用它作为核心方案。

2.2 方案二:Windows API - keybd_event(传统API)

这是Windows平台古老的API,通过user32.dll导出。它直接在系统层面模拟键盘动作。

[DllImport("user32.dll")] static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); // 按键常量 const int KEYEVENTF_KEYDOWN = 0x0000; const int KEYEVENTF_KEYUP = 0x0002; const int VK_RETURN = 0x0D; // Enter键的虚拟键码 // 模拟按下并抬起Enter键 keybd_event((byte)VK_RETURN, 0, KEYEVENTF_KEYDOWN, UIntPtr.Zero); keybd_event((byte)VK_RETURN, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);

优点

  1. 全局模拟:事件发送到系统输入队列,理论上可以影响任何程序,对焦点依赖较弱。
  2. 更底层控制:可以分别控制按键的按下(KEYEVENTF_KEYDOWN)和抬起(KEYEVENTF_KEYUP),方便实现组合键(如先按下Ctrl不放,再按C,然后释放C,再释放Ctrl)。

缺点

  1. 已过时:微软官方文档已将其标记为过时(obsolete),建议使用更新的SendInput函数。
  2. 某些游戏/安全软件会拦截:因为它是一个众所周知的、简单的模拟API,很多反作弊或安全软件会特别监控和阻止来自keybd_event的输入。

2.3 方案三:Windows API - SendInput(推荐方案)

这是keybd_event的现代替代品,功能更强大、更灵活。它可以模拟键盘、鼠标和硬件输入,并以结构体数组的形式一次性发送多个输入事件,保证了事件的原子性和时序准确性。

[DllImport("user32.dll", SetLastError = true)] static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [StructLayout(LayoutKind.Sequential)] struct INPUT { public uint type; public InputUnion U; } [StructLayout(LayoutKind.Explicit)] struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public HARDWAREINPUT hi; } [StructLayout(LayoutKind.Sequential)] struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }

优点

  1. 官方推荐:是当前Windows平台模拟输入的标准方式。
  2. 功能强大且精确:可以精确控制虚拟键码、扫描码、扩展键标志、事件时间戳等。
  3. 支持硬件模拟:可以模拟更底层的硬件扫描码,绕过一些基于虚拟键码的检测。
  4. 批量发送:能确保一系列按键(如Ctrl+C+V)的时序完全符合预期,不会被其他消息打断。

缺点

  1. 代码复杂:需要定义一系列复杂的结构体和调用约定。
  2. 仍可能被检测:虽然比keybd_event隐蔽,但高级的反作弊系统依然可以通过钩子(Hook)检测到SendInput调用。

为什么我们选择SendInput作为核心方案?综合来看,SendInput在功能性、可靠性和现代性上取得了最佳平衡。它足够底层以实现复杂的模拟,又是微软官方推荐的路径,未来兼容性有保障。我们后续的完整源码也将围绕SendInput来构建一个健壮的键盘模拟器。

3. 核心工具类设计与实现详解

理解了原理,我们开始动手构建。一个好的工具类应该具备:易用的接口、对常见任务的封装、良好的错误处理以及一定的可扩展性。下面我将分步拆解一个我称之为KeyboardSimulator的工具类。

3.1 基础常量与API声明

首先,我们需要定义大量的Windows常量(虚拟键码VK_*,标志位等)并正确声明SendInputAPI。

using System; using System.Runtime.InteropServices; using System.Threading; namespace YourNamespace.InputSimulation { /// <summary> /// 基于SendInput的键盘模拟器 /// </summary> public class KeyboardSimulator { // 输入类型常量 private const int INPUT_KEYBOARD = 1; // 键盘事件标志常量 [Flags] private enum KeyEventFlags : uint { KEYDOWN = 0x0000, EXTENDEDKEY = 0x0001, // 如果是扩展键(如右Ctrl/Alt,方向键等)需要设置 KEYUP = 0x0002, UNICODE = 0x0004, // 如果wScan指定的是Unicode字符,则设置此标志 SCANCODE = 0x0008 // 如果wScan指定的是扫描码,则设置此标志 } // 常用虚拟键码 (只列出一部分,实际需要更全) private const ushort VK_LSHIFT = 0xA0; private const ushort VK_RSHIFT = 0xA1; private const ushort VK_LCONTROL = 0xA2; private const ushort VK_RCONTROL = 0xA3; private const ushort VK_LMENU = 0xA4; // Left Alt private const ushort VK_RMENU = 0xA5; // Right Alt private const ushort VK_RETURN = 0x0D; private const ushort VK_ESCAPE = 0x1B; private const ushort VK_SPACE = 0x20; private const ushort VK_TAB = 0x09; // ... 更多键码请查阅MSDN // SendInput API声明 [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); // 获取最后一个错误信息(用于调试) [DllImport("kernel32.dll")] private static extern uint GetLastError(); // INPUT及相关结构体定义(同上文,此处省略详细定义以节省篇幅,但实际代码必须完整) [StructLayout(LayoutKind.Sequential)] private struct INPUT { ... } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { ... } [StructLayout(LayoutKind.Sequential)] private struct KEYBDINPUT { ... } } }

注意事项:结构体的内存布局(LayoutKind.Sequential)和联合体的偏移(FieldOffset(0))至关重要,必须与Windows API的定义完全一致,否则SendInput调用会失败或产生未定义行为。这是此类互操作代码最容易出错的地方。

3.2 核心发送方法:SendKeyPress

这是模拟单个按键按下并抬起的核心方法。我们将其设计为私有方法,因为它处理最底层的INPUT数组构建。

private static void SendKeyPress(ushort keyCode, bool isExtendedKey = false) { INPUT[] inputs = new INPUT[2]; // 一个按下事件,一个抬起事件 uint inputSize = (uint)Marshal.SizeOf(typeof(INPUT)); // 按下事件 inputs[0].type = INPUT_KEYBOARD; inputs[0].U.ki.wVk = keyCode; inputs[0].U.ki.wScan = 0; inputs[0].U.ki.dwFlags = isExtendedKey ? (uint)KeyEventFlags.EXTENDEDKEY : 0; inputs[0].U.ki.time = 0; inputs[0].U.ki.dwExtraInfo = IntPtr.Zero; // 抬起事件 inputs[1].type = INPUT_KEYBOARD; inputs[1].U.ki.wVk = keyCode; inputs[1].U.ki.wScan = 0; inputs[1].U.ki.dwFlags = (uint)KeyEventFlags.KEYUP; if (isExtendedKey) { inputs[1].U.ki.dwFlags |= (uint)KeyEventFlags.EXTENDEDKEY; } inputs[1].U.ki.time = 0; inputs[1].U.ki.dwExtraInfo = IntPtr.Zero; uint result = SendInput(2, inputs, (int)inputSize); if (result != 2) { uint errorCode = GetLastError(); // 在实际项目中,这里应该记录日志或抛出更详细的异常 throw new InvalidOperationException($"SendInput failed. Expected 2 events, got {result}. Error code: {errorCode}"); } }

关键点解析

  1. dwFlags:对于普通按键按下,设为0。对于抬起,必须包含KEYUP。如果模拟的是扩展键(键盘右侧的Alt、Ctrl、Insert、Delete、方向键等),则按下和抬起事件都需要包含EXTENDEDKEY标志。
  2. time:事件的时间戳。设为0表示系统将提供默认时间。在某些对时序极其敏感的场景(如游戏连招),你可以尝试用Environment.TickCount填充,但通常0即可。
  3. dwExtraInfo:额外信息,通常设为IntPtr.Zero
  4. 错误处理SendInput返回成功插入输入事件流的事件数量。我们必须检查返回值,并与预期发送的数量对比。失败时,通过GetLastError()获取错误码有助于调试(例如,某些安全软件拦截可能导致权限错误)。

3.3 上层封装:模拟字符、文本与组合键

有了底层的SendKeyPress,我们可以构建更易用的公共方法。

模拟单个按键(如回车、空格)

public static void KeyPress(KeyCode key) { // KeyCode是一个自定义枚举,将VK_常量映射为友好名称 ushort vkCode = (ushort)key; bool isExtended = IsExtendedKey(key); // 需要一个辅助方法判断是否为扩展键 SendKeyPress(vkCode, isExtended); } // 使用示例 KeyboardSimulator.KeyPress(KeyCode.Return);

模拟输入文本: 输入文本不能简单地循环调用KeyPress对应字母键,因为那无法处理大小写(需要配合Shift)。更可靠的方式是使用SendInputKEYEVENTF_UNICODE标志直接发送字符的Unicode值。

public static void SendText(string text) { if (string.IsNullOrEmpty(text)) return; foreach (char c in text) { INPUT[] inputs = new INPUT[2]; uint inputSize = (uint)Marshal.SizeOf(typeof(INPUT)); // Unicode字符按下 inputs[0].type = INPUT_KEYBOARD; inputs[0].U.ki.wVk = 0; inputs[0].U.ki.wScan = c; inputs[0].U.ki.dwFlags = (uint)(KeyEventFlags.UNICODE | KeyEventFlags.KEYDOWN); inputs[0].U.ki.time = 0; inputs[0].U.ki.dwExtraInfo = IntPtr.Zero; // Unicode字符抬起 inputs[1].type = INPUT_KEYBOARD; inputs[1].U.ki.wVk = 0; inputs[1].U.ki.wScan = c; inputs[1].U.ki.dwFlags = (uint)(KeyEventFlags.UNICODE | KeyEventFlags.KEYUP); inputs[1].U.ki.time = 0; inputs[1].U.ki.dwExtraInfo = IntPtr.Zero; SendInput(2, inputs, (int)inputSize); // 可选:在字符间加入微小延迟,模仿真人输入速度,避免被某些程序视为“洪水攻击” Thread.Sleep(10); } }

实操心得:对于输入大段文本,KEYEVENTF_UNICODE是首选,因为它直接对应字符,不受当前键盘布局(如美式 vs 英式)或大小写锁定状态的影响。但请注意,某些只接受虚拟键码输入的老旧程序可能不响应这种输入方式,这时就需要回退到模拟虚拟键码加Shift组合的方案,复杂度会高很多。

模拟组合键(如Ctrl+C): 组合键的关键在于正确的时序:先按下修饰键(Ctrl),再按下目标键(C),然后先释放目标键,再释放修饰键。

public static void KeyCombination(KeyCode modifier, KeyCode key) { ushort modVk = (ushort)modifier; ushort keyVk = (ushort)key; bool modExtended = IsExtendedKey(modifier); bool keyExtended = IsExtendedKey(key); INPUT[] inputs = new INPUT[4]; uint inputSize = (uint)Marshal.SizeOf(typeof(INPUT)); // 1. 按下修饰键 inputs[0] = CreateKeybdInput(modVk, KeyEventFlags.KEYDOWN, modExtended); // 2. 按下目标键 inputs[1] = CreateKeybdInput(keyVk, KeyEventFlags.KEYDOWN, keyExtended); // 3. 抬起目标键 inputs[2] = CreateKeybdInput(keyVk, KeyEventFlags.KEYUP, keyExtended); // 4. 抬起修饰键 inputs[3] = CreateKeybdInput(modVk, KeyEventFlags.KEYUP, modExtended); uint result = SendInput(4, inputs, (int)inputSize); if (result != 4) { /* 错误处理 */ } } // 辅助方法:创建KEYBDINPUT结构 private static INPUT CreateKeybdInput(ushort vk, KeyEventFlags flags, bool isExtended) { var input = new INPUT(); input.type = INPUT_KEYBOARD; input.U.ki.wVk = vk; input.U.ki.wScan = 0; input.U.ki.dwFlags = (uint)flags; if (isExtended) { input.U.ki.dwFlags |= (uint)KeyEventFlags.EXTENDEDKEY; } input.U.ki.time = 0; input.U.ki.dwExtraInfo = IntPtr.Zero; return input; }

4. 高级话题与实战技巧

掌握了基础模拟后,我们会遇到更复杂的需求和挑战。

4.1 焦点与窗口激活问题

即便使用SendInput,大多数输入仍然会发送到当前的前台活动窗口。因此,在模拟输入前,确保正确的窗口获得焦点是成功的前提。

[DllImport("user32.dll", SetLastError = true)] static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); public static bool BringWindowToFront(string windowTitle) { IntPtr hWnd = FindWindow(null, windowTitle); // 根据标题查找窗口句柄 if (hWnd != IntPtr.Zero) { return SetForegroundWindow(hWnd); } return false; }

注意事项SetForegroundWindow有严格的限制。在Windows Vista及以后版本,一个后台进程不能随意抢夺前台焦点,除非满足特定条件(如进程是前台进程,或收到了特定的用户输入事件)。一个常见的技巧是先模拟一个Alt键的按下(keybd_eventSendInput),然后再调用SetForegroundWindow,有时能“骗过”系统的限制。但这并非百分百可靠,是桌面自动化的一个经典难题。

4.2 应对游戏与安全软件的检测

许多在线游戏和金融软件使用反作弊或反自动化技术,它们会检测输入的来源。SendInput虽然比keybd_event好,但依然可能被检测到。更高级的方法包括:

  1. 驱动级模拟:编写或使用一个内核模式的驱动程序来模拟输入,这完全绕过了用户层的API,极难被检测。但开发复杂,有安全风险,且需要数字签名,通常用于专业外设或辅助技术软件,普通开发者慎用。
  2. 硬件级模拟:使用Arduino等微控制器模拟成一个真实的USB键盘,从物理层面“欺骗”电脑。这是最彻底的方法,完全无法被软件检测,但需要额外的硬件和嵌入式开发知识。
  3. 消息注入(SendMessage/PostMessage):直接向目标窗口进程发送WM_KEYDOWN等消息。这比SendInput更“直接”,但局限性也更大:首先,你需要目标窗口的句柄(hWnd);其次,很多程序(尤其是游戏)有自己的消息处理循环,可能不处理直接发送的消息,或者会验证消息来源;最后,跨进程发送消息可能涉及权限问题。这种方法对标准Win32控件(如记事本、对话框)可能有效,对DirectX/OpenGL游戏基本无效。

一个折中的实战技巧:对于非竞技类游戏或普通软件,可以尝试在每次按键事件之间加入随机延迟,并模拟一些“人类化”的误差(如偶尔多按一次退格键修正),这能有效绕过一些基于输入节奏和完美时序的简单检测。

public static void SendTextHumanLike(string text, int baseDelayMs = 50, int randomDelayRangeMs = 30) { Random rand = new Random(); foreach (char c in text) { SendChar(c); // 使用之前定义的SendText中的Unicode发送逻辑 int delay = baseDelayMs + rand.Next(-randomDelayRangeMs, randomDelayRangeMs); Thread.Sleep(Math.Max(10, delay)); // 确保延迟不为负 } }

4.3 异步与取消支持

长时间运行的自动化脚本可能需要支持暂停或取消。我们可以利用CancellationToken来改造我们的发送方法。

public static async Task SendTextAsync(string text, CancellationToken cancellationToken, int delayBetweenCharsMs = 20) { foreach (char c in text) { // 每次发送前检查是否取消 cancellationToken.ThrowIfCancellationRequested(); SendChar(c); // 使用异步延迟,避免阻塞线程池线程,同时支持取消 try { await Task.Delay(delayBetweenCharsMs, cancellationToken); } catch (TaskCanceledException) { // 延迟被取消,直接退出循环 break; } } }

5. 完整源码与使用示例

以下是我整合了上述所有要点的KeyboardSimulator工具类的精简版核心源码。你可以直接复制到一个新的C#类库项目中使用。

// KeyboardSimulator.cs using System; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace InputSimulation { public enum KeyCode : ushort { // 字母和数字键与ASCII码一致 A = 0x41, B = 0x42, // ... 其他字母 D0 = 0x30, // 0 D1 = 0x31, // 1 // ... 其他数字 // 功能键 F1 = 0x70, F2 = 0x71, // ... 到 F24 // 控制键 Back = 0x08, Tab = 0x09, Return = 0x0D, Shift = 0x10, Control = 0x11, Menu = 0x12, // Alt Pause = 0x13, Capital = 0x14, // Caps Lock Escape = 0x1B, Space = 0x20, Prior = 0x21, // Page Up Next = 0x22, // Page Down End = 0x23, Home = 0x24, Left = 0x25, Up = 0x26, Right = 0x27, Down = 0x28, Insert = 0x2D, Delete = 0x2E, // 小键盘 NumPad0 = 0x60, // ... 其他小键盘键 // 修饰键左右区分 LShift = 0xA0, RShift = 0xA1, LControl = 0xA2, RControl = 0xA3, LMenu = 0xA4, // Left Alt RMenu = 0xA5 // Right Alt } public static class KeyboardSimulator { // ... [所有常量、API声明、结构体定义,同上文] ... private static bool IsExtendedKey(KeyCode key) { // 判断是否为扩展键(通常是小键盘、方向键、右Ctrl/Alt等) switch (key) { case KeyCode.Insert: case KeyCode.Delete: case KeyCode.Home: case KeyCode.End: case KeyCode.Prior: // PageUp case KeyCode.Next: // PageDown case KeyCode.Left: case KeyCode.Right: case KeyCode.Up: case KeyCode.Down: case KeyCode.NumPadEnter: case KeyCode.RControl: case KeyCode.RMenu: case KeyCode.RShift: return true; default: return false; } } /// <summary> /// 模拟按下并释放一个键 /// </summary> public static void KeyPress(KeyCode key) { ushort vkCode = (ushort)key; bool isExtended = IsExtendedKey(key); SendKeyPress(vkCode, isExtended); } /// <summary> /// 模拟输入一段文本(使用Unicode方式,推荐) /// </summary> public static void SendText(string text, int delayMs = 10) { if (string.IsNullOrEmpty(text)) return; foreach (char c in text) { SendChar(c); if (delayMs > 0) { Thread.Sleep(delayMs); } } } /// <summary> /// 模拟组合键,如 Ctrl+C /// </summary> public static void KeyCombination(KeyCode modifier, KeyCode key) { // 实现细节见上文 } // ... [其他私有方法 SendKeyPress, CreateKeybdInput, SendChar] ... } }

使用示例

using InputSimulation; using System.Threading; class Program { static void Main() { // 等待5秒,让你切换到记事本或其他窗口 Thread.Sleep(5000); // 1. 输入问候语 KeyboardSimulator.SendText("Hello, this is typed by C#!\n"); // \n 代表回车 // 2. 模拟一个快捷键保存(Ctrl+S) KeyboardSimulator.KeyCombination(KeyCode.Control, KeyCode.S); Thread.Sleep(1000); // 等待保存对话框弹出 // 3. 在保存对话框中输入文件名并回车 KeyboardSimulator.SendText("my_automated_file.txt"); KeyboardSimulator.KeyPress(KeyCode.Return); Console.WriteLine("模拟输入完成。"); } }

6. 常见问题与排查技巧实录

在实际使用中,你肯定会遇到各种“诡异”的问题。下面是我踩过坑后总结的排查清单。

问题1:模拟的按键完全没有反应。

  • 检查1:窗口焦点。90%的问题出在这里。你的目标窗口真的是前台活动窗口吗?尝试在代码中手动调用BringWindowToFront,或者用Alt+Tab手动切换过去再试。
  • 检查2:权限问题。某些系统关键界面(如登录屏幕、UAC提权窗口)或受保护的管理员程序,普通权限的进程无法向其发送输入。尝试以管理员身份运行你的C#程序。
  • 检查3:反病毒/安全软件拦截。暂时禁用它们试试。特别是像某些游戏反作弊系统(如BattlEye, Easy Anti-Cheat)运行时,会严格封锁非硬件输入。
  • 检查4:SendInput调用失败。检查SendInput的返回值。如果返回0,调用GetLastError()查看错误码。错误码87(参数错误)通常意味着INPUT结构体定义不正确。

问题2:输入的内容乱码或错位(比如按A出现Q)。

  • 原因:你使用了虚拟键码(VK)方式发送文本,但当前系统的键盘布局与你预设的不同(例如,你代码里按VK_Q,但在法语布局下,这个键位对应的是‘A’)。
  • 解决方案:对于文本输入,始终优先使用KEYEVENTF_UNICODE方式(即上文SendText的实现)。它直接发送字符编码,与键盘布局无关。

问题3:组合键(如Ctrl+C)无效,只输入了‘c’。

  • 原因:时序错误。你必须确保修饰键(Ctrl)在目标键(C)按下期间一直处于按下状态。仔细检查你的KeyCombination方法,确保事件顺序是:Ctrl DOWN->C DOWN->C UP->Ctrl UP
  • 技巧:在关键事件之间可以加入微小的延迟(如Thread.Sleep(1)),虽然SendInput是原子的,但某些应用程序处理消息的速度可能跟不上。

问题4:在游戏中,方向键或数字小键盘失灵。

  • 原因:很可能漏掉了EXTENDEDKEY标志。方向键、Insert、Delete、PageUp/PageDown以及小键盘上的大部分键(除NumLock外)都是扩展键。
  • 解决:完善你的IsExtendedKey判断逻辑,确保这些键在发送时dwFlags包含了KeyEventFlags.EXTENDEDKEY

问题5:程序在发送大量按键时变得不稳定或卡顿。

  • 原因:可能没有正确处理SendInput的返回值或错误。此外,过于频繁地调用SendInput而不给系统喘息时间,可能导致输入队列堆积。
  • 优化
    1. 加入错误处理和日志记录。
    2. 在连续发送(如SendText)时,加入合理的、可配置的延迟(Thread.Sleep)。
    3. 考虑使用异步方法(SendTextAsync)来避免阻塞UI线程(如果你的程序有界面)。

一个实用的调试技巧:在开发阶段,可以创建一个简单的“回声”程序(一个只有TextBox的WinForms窗口),将你的模拟器输出定向到这里,先确保基础功能在你的可控环境内是正常的,再应用到第三方复杂程序上。

最后,模拟键盘操作是一把双刃剑,功能强大但需谨慎使用。请务必将其用于合法的自动化、辅助测试、提高工作效率等正当用途,尊重软件的使用条款,切勿用于开发游戏外挂、恶意刷票等破坏公平或违法的行为。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询