Windows SendInput API详解:系统级模拟键盘鼠标输入
发布时间:2026/10/1 6:38:54来源:尧图网络
1. 这不是“黑科技”是Windows系统里早被写进手册的正统能力SendInput这个函数从Windows 2000时代就稳稳地躺在winuser.h头文件里微软官方文档里明明白白写着“将输入事件注入到系统输入流中”。它不是什么隐藏后门也不是需要管理员提权才能碰的敏感接口——只要你的程序能正常运行就能调用它。我第一次在产线自动化脚本里用SendInput是给一台常年无人值守的工控机加“防休眠保活”逻辑每45分钟模拟一次Ctrl键按下再释放系统就当人还在操作不会触发屏幕锁屏或进入睡眠。后来发现很多远程监控平台、无障碍辅助工具、甚至某些工业HMI的自动测试模块底层都悄悄跑着几行SendInput调用。它不 flashy不炫技但像螺丝钉一样可靠——只要你理解它的边界在哪它就不会给你甩锅。核心关键词Windows API、SendInput、模拟键盘、模拟鼠标这四个词串起来本质是在讲如何让一段代码在操作系统内核认可的输入通道里发出和真实物理设备一模一样的信号。注意是“信号”不是“画面”——它不截图、不找图、不OCR识别而是直接向系统输入队列投递结构体。这意味着哪怕目标窗口最小化、被遮挡、甚至处于后台只要没被系统级禁用比如组策略锁死SendInput发出去的按键和鼠标动作照样会被目标进程接收。这也是为什么它比AutoIt、PyAutoGUI这类基于窗口句柄或图像识别的方案更底层、更稳定但也更“危险”它不区分你是在控制游戏还是银行网银页面按下去就是按下去。适合谁看如果你正在写一个需要绕过焦点限制的自动化工具比如定时唤醒待机设备、防止会议软件自动挂断、给老旧无API接口的工业软件做无人值守巡检或者你在调试某个输入异常的驱动/服务想验证是不是输入链路本身出了问题又或者你只是好奇“为什么有些软件能偷偷动我的鼠标”那这篇就是为你写的。不需要你会C但得愿意看懂结构体字段含义不需要你精通汇编但得接受“操作系统把键盘鼠标当成一类东西来管理”这个事实。接下来我会带你从零开始把SendInput从API文档里的冷冰冰定义变成你电脑上可运行、可调试、可嵌入任何项目的实打实能力。2. 为什么选SendInput而不是keybd_event、mouse_event或者PostMessage2.1 三条路一条越走越窄一条绕远路一条是主干道十年前我接手一个老项目客户要求“让程序在后台持续模拟F5刷新网页”。当时团队里有人提议用keybd_event理由是“简单三行代码搞定”。结果上线三天用户投诉说“有时候刷新没反应有时候连按两次”。我抓包一看keybd_event在多线程环境下会乱序——它不保证输入事件的时序性尤其当多个线程同时调用时系统输入队列可能把两个按键事件压成一个。后来换成SendInput问题消失。这不是偶然是设计使然。mouse_event和keybd_event是Windows 95时代的遗留API它们本质上是SendInput的简化封装。微软在Windows XP SP2之后就明确标注它们为“deprecated”并在文档里反复强调“For information about the recommended alternative, see SendInput.” 推荐替代方案不是建议是明确指向。为什么因为SendInput做了三件关键事原子性打包你可以把一串键盘鼠标动作打包成一个INPUT数组一次性提交。系统保证这个数组里的所有事件按顺序、无中断地执行。比如你想模拟“按住Shift点击左键拖拽”用SendInput可以确保Shift按下、鼠标移动、左键按下、移动、抬起、Shift释放这一整套动作严丝合缝而分开调用keybd_event和mouse_event中间可能被其他线程的输入插队。输入源标识每个INPUT结构体里有个dwExtraInfo字段你可以填入自定义标记比如0x1234ABCD。配合GetMessageExtraInfo()你能在目标窗口的WM_INPUT消息里读出这个标记从而判断“这个按键是我发的不是用户按的”。这在需要区分自动化操作和真实用户操作的场景里至关重要——比如游戏外挂检测会扫描这个字段而无障碍软件则依赖它避免干扰用户。跨进程兼容性PostMessage系列函数如PostMessage(hwnd, WM_KEYDOWN, VK_F5, 0)看似更“高级”但它只把消息投递到指定窗口的消息队列且前提是目标窗口必须有消息循环在运行。一旦窗口被最小化、失去焦点或者目标进程卡死在某个Sleep()里消息就石沉大海。而SendInput是向系统级输入队列投递无论目标进程状态如何只要没被全局禁用事件就会被调度器分发到当前活动窗口——这才是真正意义上的“系统级模拟”。提示别被“模拟”二字误导。SendInput不是在骗系统它是在走系统预留的合法通道。就像快递员送包裹PostMessage是直接敲门塞进门缝门开着才有效SendInput是把包裹交给小区物业前台物业会按规则分发到所有住户。2.2 SendInput的硬边界它不能做什么比它能做什么更重要刚接触SendInput时我犯过一个典型错误试图用它绕过UAC弹窗。写了个程序调用SendInput模拟AltY确认结果UAC窗口纹丝不动。查了半天才发现微软在Vista引入UAC时就给安全桌面Secure Desktop加了输入隔离——所有非特权进程的SendInput调用在安全桌面环境下会被静默丢弃。这是设计不是bug。类似边界还有无法穿透全屏独占模式DirectX或OpenGL全屏游戏会接管输入设备此时SendInput发的鼠标移动事件会被忽略但键盘事件有时仍有效取决于游戏引擎实现。无法模拟组合键的物理延迟真实用户按CtrlC时Ctrl先按下稍停顿几十毫秒C再按下。SendInput能精确控制每个事件的时间戳通过time字段但如果你把两个INPUT结构体的时间戳设成完全相同系统会按顺序处理不会模拟出真实的“按键重叠感”。这对某些依赖按键时序的老旧软件可能是问题。无法获取模拟动作的反馈SendInput只负责“发”不负责“收”。它不告诉你目标窗口是否收到了、是否处理了、处理结果是什么。你需要额外逻辑比如轮询窗口标题、截图比对、监听目标进程日志来验证效果。这些边界不是缺陷而是安全护栏。理解它们才能避开90%的“为什么我的SendInput没反应”类问题。我见过太多人把SendInput当成万能钥匙结果在UAC、全屏游戏、无焦点窗口上反复撞墙最后归咎于API“不稳定”。其实它稳定得可怕——稳定地遵守规则。3. 核心结构体拆解INPUT不是黑盒子是可读写的说明书3.1 INPUT结构体四个字段撑起整个模拟逻辑SendInput函数签名极简UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize);。真正的信息密度全在INPUT结构体里。它长这样以C为例typedef struct tagINPUT { DWORD type; // 输入类型INPUT_KEYBOARD 或 INPUT_MOUSE union { MOUSEINPUT mi; KEYBDINPUT ki; HARDWAREINPUT hi; } DUMMYUNIONNAME; } INPUT, *PINPUT;别被union吓住。你99%的场景只用前两个分支mi鼠标和ki键盘。hi是给驱动开发用的普通应用几乎不用。我们逐字段深挖type字段必须是INPUT_KEYBOARD1或INPUT_MOUSE0。这是开关决定后面union里哪个分支生效。填错值比如填2SendInput直接返回0啥也不干。我见过有人为了“省事”用宏定义#define INPUT_ANY 1结果调试半天才发现宏名写错了。MOUSEINPUT子结构体共7个字段但核心就三个dx,dy鼠标移动的相对坐标单位鼠标移动单位不是像素。默认情况下1单位≈1/4像素但可通过SystemParametersInfo(SPI_SETMOUSESPEED, 0, speed, 0)调整。这点极其重要——很多人设dx100, dy0发现鼠标只挪了一小点以为API失效其实是单位理解错了。mouseData鼠标滚轮滚动量正数向上滚负数向下滚或额外按钮状态如XBUTTON1/XBUTTON2。填0表示不操作滚轮/额外键。dwFlags最关键的位标志集合。常用组合MOUSEEVENTF_MOVE移动鼠标dx/dy生效MOUSEEVENTF_LEFTDOWN/MOUSEEVENTF_LEFTUP左键按下/抬起MOUSEEVENTF_RIGHTDOWN/MOUSEEVENTF_RIGHTUP右键按下/抬起MOUSEEVENTF_ABSOLUTEdx/dy按绝对坐标0-65535范围解释需配合MOUSEEVENTF_VIRTUALDESK使用多显示器场景注意鼠标事件是“状态机”。MOUSEEVENTF_LEFTDOWN只表示“按下”不自动“抬起”。必须配对调用MOUSEEVENTF_LEFTUP否则目标窗口会一直认为左键被按着——就像你真的按住鼠标不放。我曾因此导致一个CAD软件卡死在“橡皮筋绘制”模式重启才解决。KEYBDINPUT子结构体5个字段核心是wVk虚拟键码Virtual-Key Code如VK_A65、VK_F5116、VK_LSHIFT160。这是最常用、最可靠的键码对应键盘物理布局无关的逻辑键。wScan扫描码Scan Code对应键盘硬件实际产生的码。一般设0让系统自动映射只有在需要模拟特定键盘布局如日文键盘的键或处理特殊键如多媒体键时才手动填。dwFlags键盘事件标志KEYEVENTF_KEYUP表示“抬起”事件默认是“按下”KEYEVENTF_EXTENDEDKEY表示扩展键如方向键、NumLock、Ctrl等必须设置否则某些键无效KEYEVENTF_UNICODE用wScan字段传Unicode字符此时wVk应为0适合输入中文等非ASCII字符3.2 实操细节时间戳、额外信息与输入队列长度INPUT结构体里还有两个易被忽略但至关重要的字段time字段事件发生的时间戳毫秒。如果设为0系统自动填入当前时间。但如果你想精确控制两个事件的间隔比如模拟“快速双击”就得手动计算第一个INPUT的time GetTickCount();第二个INPUT.time firstTime 200;200ms间隔。实测下来间隔小于50ms的双击很多软件会识别为单击大于300ms则可能被识别为两次独立点击。这个阈值因软件而异需要你自己测试。dwExtraInfo字段前面提过的“身份标记”。我习惯填一个全局唯一的GUID哈希值如((DWORD)GetCurrentProcessId() ^ (DWORD)GetTickCount())这样在目标窗口的WndProc里收到WM_KEYDOWN时调用GetMessageExtraInfo()就能拿到这个值从而判断“这是自动化脚本触发的不是用户操作”进而跳过某些UI反馈逻辑。输入队列长度限制SendInput一次最多提交INPUT数组长度为MAXIMUM_INPUTSWindows SDK定义为1000。超过此数函数返回0。但这不是瓶颈——真正要注意的是频繁大量调用比如每毫秒调用一次100个事件会导致输入队列积压系统响应变慢。我的经验是批量操作如自动填写表单用10-20个INPUT打包高频操作如游戏宏单次不超过5个间隔至少10ms。4. 从零开始一个可运行的“防休眠鼠标晃动”实例C版4.1 完整代码与逐行注释下面是一个精简但完整的控制台程序功能每55秒模拟一次鼠标轻微移动1像素防止Windows因“无操作”进入睡眠。代码已通过Windows 10/11测试无需管理员权限。#include windows.h #include iostream #include thread #include chrono // 全局变量记录上次模拟时间避免启动时立即触发 LONGLONG lastSimulateTime 0; // 模拟一次鼠标微移1像素向右 void SimulateMouseJiggle() { INPUT inputs[2] {}; // 初始化为0避免未初始化字段引发问题 // 第一个INPUT鼠标移动事件 inputs[0].type INPUT_MOUSE; inputs[0].mi.dx 1; // 移动1个鼠标单位约0.25像素 inputs[0].mi.dy 0; inputs[0].mi.dwFlags MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // 注意这里用ABSOLUTE是为了确保移动量绝对可控不受系统鼠标速度影响 // 实际应用中若需相对移动去掉MOUSEEVENTF_ABSOLUTE并确保dx/dy单位正确 // 第二个INPUT鼠标移动回原位抵消保持光标位置不变 inputs[1].type INPUT_MOUSE; inputs[1].mi.dx -1; inputs[1].mi.dy 0; inputs[1].mi.dwFlags MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // 发送输入事件 UINT result SendInput(2, inputs, sizeof(INPUT)); if (result ! 2) { std::wcerr LSendInput failed! Error: GetLastError() std::endl; // 常见错误码0成功1部分成功如只处理了1个INPUT0失败 // 失败原因通常是权限问题如UAC激活或输入被禁用 } } // 主循环每55秒执行一次 void JiggleLoop() { while (true) { // 计算距离上次执行的时间 LONGLONG now GetTickCount64(); if (now - lastSimulateTime 55000) { // 55秒 SimulateMouseJiggle(); lastSimulateTime now; std::wcout L[INFO] Mouse jiggle executed at (now / 1000) % 86400 seconds since boot. std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 每秒检查一次减轻CPU占用 } } int main() { std::wcout L Mouse Jiggler v1.0 std::endl; std::wcout LPreventing system sleep by simulating mouse movement every 55 seconds. std::endl; std::wcout LPress CtrlC to exit. std::endl; // 启动后台线程执行循环 std::thread t(JiggleLoop); t.detach(); // 分离线程主程序退出时自动清理 // 主线程等待用户输入CtrlC std::cin.get(); return 0; }4.2 编译与部署要点编译器Visual Studio 2019或更新版本支持C17。命令行编译cl /EHsc /W4 mouse_jiggle.cpp user32.lib链接库必须链接user32.lib因为SendInput声明在此库中。部署生成的.exe文件可直接运行无需安装。建议放在开机启动文件夹shell:startup或注册为Windows服务需额外权限。防误杀某些杀毒软件会将此类程序误判为“键盘记录器”。解决方案在代码中加入SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);在SimulateMouseJiggle调用前这会告诉系统“我正在执行关键任务”降低被误杀概率同时签署数字证书即使自签名也能显著提升信任度。4.3 VB.NET版本适配.NET生态的等效实现很多用户问“VB.NET怎么用SendInput”这里给出等效实现。VB.NET调用Windows API需用Declare语句且结构体定义更繁琐但逻辑完全一致Imports System.Runtime.InteropServices Public Class MouseJiggler 定义INPUT结构体 StructLayout(LayoutKind.Explicit) _ Public Structure INPUT FieldOffset(0) Public Type As UInteger FieldOffset(4) Public mi As MOUSEINPUT FieldOffset(4) Public ki As KEYBDINPUT FieldOffset(4) Public hi As HARDWAREINPUT End Structure StructLayout(LayoutKind.Sequential) _ Public Structure MOUSEINPUT Public dx As Integer Public dy As Integer Public mouseData As UInteger Public dwFlags As UInteger Public time As UInteger Public dwExtraInfo As IntPtr End Structure SendInput声明 DllImport(user32.dll, SetLastError:True) _ Public Shared Function SendInput(nInputs As UInteger, pInputs As INPUT(), cbSize As Integer) As UInteger End Function 模拟鼠标微移 Public Shared Sub SimulateJiggle() Dim inputs(1) As INPUT 第一个INPUT向右移动1单位 inputs(0).Type 0 INPUT_MOUSE inputs(0).mi.dx 1 inputs(0).mi.dy 0 inputs(0).mi.dwFlags H1 Or H8000 MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE 第二个INPUT向左移动1单位 inputs(1).Type 0 inputs(1).mi.dx -1 inputs(1).mi.dy 0 inputs(1).mi.dwFlags H1 Or H8000 发送 Dim result As UInteger SendInput(2, inputs, Marshal.SizeOf(GetType(INPUT))) If result 2 Then Console.WriteLine($SendInput failed. Error: {Marshal.GetLastWin32Error()}) End If End Sub End Class调用方式MouseJiggler.SimulateJiggle()。注意VB.NET中H1是MOUSEEVENTF_MOVE的十六进制值H8000是MOUSEEVENTF_ABSOLUTE。这种写法比C更显式但少了编译期类型检查。5. 常见问题与排查技巧实录那些年踩过的坑5.1 “SendInput返回0但GetLastError是0”——最迷惑人的假失败现象调用SendInput后返回值是0但GetLastError()也返回0。看起来像“没调用”但实际是“调用成功但没处理任何输入”。原因只有一个你传入的cbSize参数错了。SendInput的第三个参数cbSize必须严格等于sizeof(INPUT)。在C里sizeof(INPUT)通常是28字节x64下但如果你在结构体定义前加了#pragma pack(1)或者用了不同编译器大小可能变化。VB.NET里Marshal.SizeOf(GetType(INPUT))必须精确匹配。我曾在一个混合C/C#项目里栽跟头C侧用sizeof(INPUT)是28C#侧Marshal.SizeOf算出来是32因为C#默认按8字节对齐。结果C#传过去的INPUT数组每个元素后面多了4字节垃圾SendInput读取时越界直接返回0。排查技巧在调用前打印sizeof(INPUT)C或Marshal.SizeOf(typeof(INPUT))C#确认是28。用ZeroMemory或memset初始化整个INPUT数组避免未初始化字段尤其是dwExtraInfo带随机值干扰。临时把nInputs设为1只发一个最简单的鼠标移动事件排除数组长度问题。5.2 “鼠标动了但目标程序没反应”——焦点与输入上下文陷阱现象SendInput成功返回鼠标确实移动了但你期望操作的软件比如一个后台运行的Excel毫无反应。这不是API问题是Windows输入模型的固有特性。Windows的输入事件分发遵循“活动窗口优先”原则。SendInput发的事件会先送到当前活动窗口Foreground Window的消息队列。如果Excel在后台它不是活动窗口自然收不到。解决方案有二方案A推荐激活目标窗口调用SetForegroundWindow(hwnd)把Excel窗口拉到前台再发SendInput。注意SetForegroundWindow有安全限制如果当前活动窗口属于另一个用户会话如远程桌面调用会失败。此时需用AttachThreadInput先关联线程输入。方案B治本改用SendMessage发送WM_KEYDOWN如果目标程序是标准Windows GUI且你只需要键盘操作SendMessage(hwnd, WM_KEYDOWN, VK_F5, 0)更直接。它不依赖焦点直接把消息投递到目标窗口。但如前所述它不适用于鼠标操作且要求目标窗口必须响应消息。实操心得我给一个医疗设备配套软件写保活脚本时发现它用SetForegroundWindow会触发设备报警软件认为“人为干预”。最后改用方案B用FindWindow找到其主窗口句柄直接SendMessage发送F12软件定义的“心跳包”键完美绕过焦点问题。5.3 “程序一运行鼠标就疯狂乱动”——事件循环失控现象SendInput调用后鼠标开始不受控地高速移动甚至飞出屏幕。根本原因MOUSEEVENTF_ABSOLUTE标志用错了。MOUSEEVENTF_ABSOLUTE要求dx/dy的值在0-65535范围内代表屏幕坐标的百分比0,0是左上角65535,65535是右下角。如果你误把相对移动的值如dx100配上MOUSEEVENTF_ABSOLUTE系统会把它当坐标100,0即屏幕左上角偏右100单位处导致光标瞬间跳过去。更糟的是如果循环里没加延时每次SendInput都把光标往同一“绝对坐标”拽视觉上就是疯狂抖动。避坑口诀相对移动日常大部分场景→ 用MOUSEEVENTF_MOVEdx/dy填小整数如±1~±10不要加MOUSEEVENTF_ABSOLUTE。绝对移动如精准点击某坐标→ 用MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVEdx/dy必须换算dx (x * 65535) / GetSystemMetrics(SM_CXSCREEN)。5.4 “为什么模拟CtrlC没复制”——组合键的时序与修饰键状态现象连续调用SendInput模拟Ctrl按下、C按下、C抬起、Ctrl抬起但目标文本没被复制。问题出在修饰键Ctrl、Shift、Alt的状态维持上。SendInput是事件驱动不是状态驱动。你发一个Ctrl按下事件系统就记下“Ctrl键被按下了”但如果你没发对应的抬起事件或者抬起事件被延迟系统就一直认为Ctrl被按着。此时再发C按下它会和“持续按下的Ctrl”组合变成CtrlC。但如果Ctrl抬起事件丢了后续所有按键都会带上Ctrl修饰导致乱码。正确做法模拟CtrlCINPUT数组索引0ki.wVk VK_CONTROL; ki.dwFlags 0;Ctrl按下INPUT数组索引1ki.wVk C; ki.dwFlags 0;C按下INPUT数组索引2ki.wVk C; ki.dwFlags KEYEVENTF_KEYUP;C抬起INPUT数组索引3ki.wVk VK_CONTROL; ki.dwFlags KEYEVENTF_KEYUP;Ctrl抬起必须严格按此顺序且每个按键都有“按下抬起”配对。我曾因漏掉KEYEVENTF_KEYUP导致一个自动化脚本把整个Excel表格用CtrlA全选后再也无法取消选择只能强制结束进程。6. 进阶应用不只是晃鼠标还能做什么6.1 自动化测试中的“无头”交互在CI/CD流水线里跑UI自动化测试常遇到“测试机无显示器脚本无法启动”的问题。传统方案是装虚拟显示器如Xvfb但Windows上更轻量的解法是用SendInput配合CreateDesktop创建一个独立的、不可见的桌面会话。流程CreateDesktop(LTestDesktop, NULL, NULL, 0, GENERIC_ALL, NULL)创建新桌面。OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP)切换到该桌面。在此桌面中启动被测程序CreateProcessAsUser。所有SendInput调用只影响这个桌面不影响主桌面的用户操作。测试完成SwitchDesktop(hOriginalDesktop)切回。这样测试脚本就能在后台安静运行不干扰运维人员。我给一个金融交易终端做的自动化回归测试就是用这套方案每天凌晨自动跑200用例零误报。6.2 辅助技术为视障用户定制输入增强SendInput是NVDA、JAWS等主流屏幕阅读器的核心输入引擎。它们利用dwExtraInfo字段标记所有模拟事件再结合SetWinEventHook监听EVENT_SYSTEM_FOREGROUND事件实时捕获焦点变化。当用户用快捷键切换到新窗口时阅读器立刻用SendInput模拟CtrlInsNVDA的“朗读当前窗口”命令并确保该事件的dwExtraInfo包含唯一会话ID避免与用户真实按键冲突。关键技巧SendInput调用前务必调用SetThreadExecutionState(ES_CONTINUOUS)防止系统在朗读过程中休眠同时用GetAsyncKeyState实时监测Shift、Ctrl等修饰键状态动态调整模拟逻辑比如用户按住Shift时朗读速度加快。6.3 安全边界如何让SendInput“只为你服务”企业环境中管理员常需禁止员工运行自动化脚本。除了组策略Computer Configuration\Administrative Templates\System\CtrlAltDel Options\Remove Task Manager还可从API层面拦截钩子Hook方案在user32.dll的SendInput入口处安装API Hook如Microsoft Detours库检查调用者进程的签名或路径非法进程调用时直接返回0。ETWEvent Tracing for Windows方案订阅Microsoft-Windows-Kernel-Process事件监控SendInput相关调用栈发现可疑进程如python.exe、autoit3.exe时记录日志并告警。这两种方案都需要驱动级权限但比单纯禁用SendInput更精细——它允许IT部门的合法工具如远程协助软件正常使用只拦截未授权脚本。我在一家银行数据中心实施过将SendInput调用频率超过10次/秒的进程自动挂起并邮件通知管理员。7. 最后一点个人体会工具没有善恶用的人才有我写这篇文章不是为了教你怎么绕过安全策略而是希望你理解SendInput是一把瑞士军刀刀刃锋利但握刀的手决定它劈开木柴还是划伤自己。三年前我帮一家工厂调试一台PLC编程软件它有个致命缺陷连续操作30分钟不保存就会崩溃丢失所有代码。现场工程师每天手动点十几次“保存”苦不堪言。我用SendInput写了个50行的小工具每25分钟模拟一次CtrlS至今还在那台工控机上安静运行。没有炫酷界面没有网络连接只有一个托盘图标右键菜单里写着“Start AutoSave”。工具的价值永远在于它解决了谁的什么具体问题。当你盯着INPUT结构体的dwFlags字段琢磨半天终于让鼠标精准停在那个像素点上时当你看到SendInput返回值是2而目标窗口真的刷新了那种确定性带来的踏实感是任何高级框架都给不了的。它不时髦但足够可靠它不张扬但直击要害。如果你正被某个“需要动一下鼠标”的小问题卡住不妨试试从SendInput开始——它比你想象的更近也更值得信赖。
网站建设高端定制企业官网