☰
Win10模拟触摸驱动:基于InjectTouchInput的用户态注入实现
2026/10/8 9:51:05 网站建设 项目流程

简介:一份面向 Windows 10 64 位环境的模拟触摸驱动资源,核心解决 MultiTouchVista 原版在 Win10 下常见的 devcon failed 安装失败问题,可应用于触屏应用调试、多指手势验证以及无实体触屏设备时的功能演示与测试。压缩包共 85 个文件,体积仅 7.36MB;除核心的 SYS/INF 驱动文件外,还包含 DLL 运行库、EXE 主程序与配置工具、XML 参数配置、CMD 安装脚本以及 PDB 调试符号。x86/x64 目录均有覆盖,驱动与服务组件、WPF 配置界面、控制台工具等模块齐全,便于按需选取;PDB 符号和相关组件也能帮助进阶用户理解驱动工作方式。附带 WPF 文档说明多点触控模拟环境的搭建思路,并给出安装时须以管理员权限运行的排错经验,避免因权限不足再次触发 devcon failed。整体内容紧凑、指向明确,已有 4265 人浏览学习,适合需要快速搞定模拟触摸驱动或正在排查同类问题的用户参考。

1. 模拟触摸驱动在 Win10 上到底解决什么问题

一台没有触摸屏的旧笔记本,一个装了 Win10 的虚拟机,或是一块只有 VGA 输入的工控屏,想跑一个只能用手指滑动的手势应用,鼠标按下去常常没有任何反应。因为应用等的是触摸事件,不是鼠标消息。模拟触摸驱动的核心,就是把鼠标坐标、脚本坐标或按钮状态,包装成 Windows 10 能识别的“手指触点”,让系统以为屏幕上真的有一根手指在点。它解决的不是硬件屏缺失,而是输入链路错位:你缺的是触屏设备,但真正缺的是把坐标“翻译”成触摸报告的通道。这个方案特别适合 Win10 虚拟机调试、老设备改造、自动化触控回归测试,以及不想为触摸功能换硬件的预算型项目。

2. 原理先行:Win10 的触摸链路与三条模拟路线

2.1 触摸屏不是鼠标:HID 数字化器链路

Windows 10 里,一颗真正的触摸屏在设备管理器中显示为“符合 HID 标准的触摸屏”,属于 HIDClass,设备实例路径里通常带HID\VID_...。它不是一个普通鼠标,而是一个 HID 数字化器(Digitizer)。触摸屏固件通过 HID 报告描述符上报触点集合,包括 contact ID、X/Y、接触面积、压力、方向角。系统拿到这些原始字段后,在输入栈里合成为两类事件:老式 Win32 的WM_TOUCH,以及 Win8/10 开始主推的WM_POINTER。

这解释了为什么单纯用SendInput发鼠标事件没用:鼠标事件走的是MOUSE_INPUT流,应用层的手势识别器看的是TOUCH_INPUT流。模拟触摸驱动要做的事,就是向触摸输入流里灌数据,而不是向鼠标流里灌数据。很多人第一次做这类驱动时习惯性写“生成鼠标事件”,结果 Win10 上画图板能画,但商店应用始终不响应,原因就在这里:事件类型错了。

2.2 三条路线:内核驱动、用户态注入、系统开关

做模拟触摸,业内常见有三条路线,选型时先看权限要求和交付环境。

路线实现位置难度签名要求适用场景
内核 KMDF/HID 过滤驱动内核态,做一个虚拟 HID 数字化器高需要 EV 签名锁定屏幕、登录界面也要触摸
用户态注入 API调用InitializeTouchInjection+InjectTouchInput中不需要驱动签名普通桌面应用、自动化测试
注册表/兼容开关修改系统触摸兼容选项低无特定老游戏、平板模式开关

内核驱动是最“正规”的模拟触摸驱动:它创建一个虚拟 HID 触摸屏,系统把这个设备当成真实触摸屏枚举,任何进程、任何会话都能收到触摸。代价也很明显:Win10 64 位强制驱动签名,测试模式还有安全引导拦路,一台客户机器上部署成本非常高。而且只要系统大版本更新,过滤驱动可能直接蓝屏。这里说的“win10 可用”多数情况下不是指这种重型方案。

2.3 为什么大多数“win10 可用”的驱动其实走用户态注入

Windows 10 1607 版本开始,微软开放了一组触摸注入 API:InitializeTouchInjection和InjectTouchInput。这组 API 允许一个普通用户态进程声明“我要虚拟出最多 N 根手指”,然后把触点坐标、压力、接触矩形打包提交给系统。系统接到这些数据后,会走和真实触摸屏几乎相同的识别链路,生成WM_POINTER和WM_TOUCH。

它的局限是必须运行在交互式桌面会话中,且多数情况下需要管理员权限,但它绕开了驱动签名。实际项目里,所谓“模拟触摸驱动”常常是一个开机自启的后台程序:负责监听鼠标、串口、网络指令或脚本文件,然后转成触摸注入。它本质上是“用户态驱动”,但因为以系统服务/计划任务方式常驻,所以在产品交付时被叫成驱动。后面章节给的最小可复现代码就基于这组 API,配合计划任务做成一个 Win10 可用的后台模拟触摸组件。

3. 动手写最小模拟触摸程序:基于 InjectTouchInput 的 C# 代码

3.1 项目准备:P/Invoke 与结构体定义

先创建一个 C# 控制台工程,.NET Framework 4.8 或 .NET 6/8 都可以,关键是要编译成 x64。以下代码定义触摸注入所需的原生结构和 P/Invoke 入口。

using System; using System.ComponentModel; using System.Runtime.InteropServices; namespace SimTouchDriver { public static class NativeTouch { public const uint PT_TOUCH = 0; public const uint TOUCH_FEEDBACK_DEFAULT = 0x00000001; public const uint TOUCH_FEEDBACK_NONE = 0x00000003; public const uint POINTER_FLAG_DOWN = 0x00000001; public const uint POINTER_FLAG_UPDATE = 0x00000002; public const uint POINTER_FLAG_UP = 0x00000004; public const uint POINTER_FLAG_INRANGE = 0x00000008; public const uint TOUCH_MASK_CONTACTAREA = 0x00000001; public const uint TOUCH_MASK_ORIENTATION = 0x00000002; public const uint TOUCH_MASK_PRESSURE = 0x00000004; [DllImport("user32.dll")] public static extern bool InitializeTouchInjection(uint maxCount, uint dwMode); [DllImport("user32.dll")] public static extern bool InjectTouchInput(uint count, POINTER_TOUCH_INFO[] contacts); [DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr value); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [StructLayout(LayoutKind.Sequential)] public struct POINTER_INFO { public uint pointerType; public uint pointerId; public uint frameId; public uint pointerFlags; public IntPtr sourceDevice; public IntPtr hwndTarget; public POINT ptPixelLocation; public POINT ptHoverLocation; public uint dwTime; public uint historyCount; public long performanceCount; public uint buttonChangeType; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public uint[] buttons; } [StructLayout(LayoutKind.Sequential)] public struct POINTER_TOUCH_INFO { public POINTER_INFO pointerInfo; public uint touchFlags; public uint touchMask; public RECT rcContact; public RECT rcContactRaw; public uint orientation; public uint pressure; } private static uint _frame; public static void SetupDpiAwareness() { // PER_MONITOR_AWARE_V2,避免注入坐标被系统二次缩放 SetProcessDpiAwarenessContext(new IntPtr(-4)); } public static bool Init() { // 声明最多 10 根虚拟手指 return InitializeTouchInjection(10, TOUCH_FEEDBACK_DEFAULT); } public static void TouchDown(uint pointerId, int x, int y) { var c = new POINTER_TOUCH_INFO[1]; c[0].pointerInfo.pointerType = PT_TOUCH; c[0].pointerInfo.pointerId = pointerId; c[0].pointerInfo.frameId = ++_frame; c[0].pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE; c[0].pointerInfo.ptPixelLocation = new POINT { X = x, Y = y }; c[0].pointerInfo.dwTime = (uint)Environment.TickCount; c[0].touchFlags = 0; c[0].touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE; c[0].rcContact = new RECT { Left = x - 15, Top = y - 15, Right = x + 15, Bottom = y + 15 }; c[0].rcContactRaw = c[0].rcContact; c[0].orientation = 0; c[0].pressure = 1024; if (!InjectTouchInput(1, c)) throw new Win32Exception(Marshal.GetLastWin32Error()); } public static void TouchUpdate(uint pointerId, int x, int y) { var c = new POINTER_TOUCH_INFO[1]; c[0].pointerInfo.pointerType = PT_TOUCH; c[0].pointerInfo.pointerId = pointerId; c[0].pointerInfo.frameId = ++_frame; c[0].pointerInfo.pointerFlags = POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE; c[0].pointerInfo.ptPixelLocation = new POINT { X = x, Y = y }; c[0].pointerInfo.dwTime = (uint)Environment.TickCount; c[0].touchFlags = 0; c[0].touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE; c[0].rcContact = new RECT { Left = x - 15, Top = y - 15, Right = x + 15, Bottom = y + 15 }; c[0].rcContactRaw = c[0].rcContact; c[0].orientation = 0; c[0].pressure = 1024; if (!InjectTouchInput(1, c)) throw new Win32Exception(Marshal.GetLastWin32Error()); } public static void TouchUp(uint pointerId, int x, int y) { var c = new POINTER_TOUCH_INFO[1]; c[0].pointerInfo.pointerType = PT_TOUCH; c[0].pointerInfo.pointerId = pointerId; c[0].pointerInfo.frameId = ++_frame; c[0].pointerInfo.pointerFlags = POINTER_FLAG_UP; c[0].pointerInfo.ptPixelLocation = new POINT { X = x, Y = y }; c[0].pointerInfo.dwTime = (uint)Environment.TickCount; c[0].touchFlags = 0; c[0].touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE; c[0].rcContact = new RECT { Left = x - 15, Top = y - 15, Right = x + 15, Bottom = y + 15 }; c[0].rcContactRaw = c[0].rcContact; c[0].orientation = 0; c[0].pressure = 0; if (!InjectTouchInput(1, c)) throw new Win32Exception(Marshal.GetLastWin32Error()); } } }

这段代码的逻辑说明:InitializeTouchInjection(10, TOUCH_FEEDBACK_DEFAULT)的第一个参数表示你要最多模拟几根手指,按需设置为 2 或 10;TOUCH_FEEDBACK_DEFAULT会让 Win10 显示一个半透明的触点反馈圆圈,方便调试。每个触点用一个pointerId区分,这个 ID 在你模拟“按下 -> 移动 -> 抬起”的整个生命周期里必须保持不变。ptPixelLocation是屏幕坐标系下的物理像素坐标,不是窗口客户区坐标;rcContact是触点接触面积,Win10 的手势识别器会参考它做指尖区域计算。pressure在触摸场景建议填 1024 这个上限,orientation填 0 即可,只有手写笔模拟才需要考虑旋转角。

3.2 一次完整的“按下-移动-抬起”

在Main里调用上述方法,就能让系统收到一根虚拟手指的完整动作:

using System; using System.Threading; class Program { static void Main() { NativeTouch.SetupDpiAwareness(); if (!NativeTouch.Init()) { Console.WriteLine("InitializeTouchInjection failed: " + Marshal.GetLastWin32Error()); return; } // 模拟在屏幕 (500, 400) 处按下并滑动到 (700, 600) NativeTouch.TouchDown(0, 500, 400); Thread.Sleep(200); for (int i = 1; i <= 20; i++) { int x = 500 + i * 10; int y = 400 + i * 10; NativeTouch.TouchUpdate(0, x, y); Thread.Sleep(16); } NativeTouch.TouchUp(0, 700, 600); Console.WriteLine("done"); } }

这里的pointerId=0表示第一根手指;如果要做双指缩放,第二根手指用pointerId=1,两指的frameId可以递增,但每个触点必须单独调用InjectTouchInput。Thread.Sleep(16)是参考值,Win10 的触摸刷新率多数在 60Hz 左右,按 16 毫秒一帧的节奏移动不容易产生跳变。实际项目中我一般会把移动轨迹做插值,避免两帧之间跨度过大导致手势识别器把滑动判成甩动。

3.3 把鼠标移动变成连续触摸:循环与参数调整

只跑一次注入没有实用价值。常见做法是把鼠标左键当手指,做一个常驻循环。这里给出一个最简的单指鼠标模拟循环:

using System; using System.Runtime.InteropServices; using System.Threading; class Program { [DllImport("user32.dll")] static extern bool GetCursorPos(out NativeTouch.POINT pos); [DllImport("user32.dll")] static extern short GetAsyncKeyState(int vKey); const int VK_LBUTTON = 0x01; static void Main() { NativeTouch.SetupDpiAwareness(); if (!NativeTouch.Init()) { Console.WriteLine("init failed"); return; } bool fingerDown = false; while (true) { bool leftPressed = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0; GetCursorPos(out NativeTouch.POINT pos); if (leftPressed && !fingerDown) { NativeTouch.TouchDown(0, pos.X, pos.Y); fingerDown = true; } else if (leftPressed && fingerDown) { NativeTouch.TouchUpdate(0, pos.X, pos.Y); } else if (!leftPressed && fingerDown) { NativeTouch.TouchUp(0, pos.X, pos.Y); fingerDown = false; } Thread.Sleep(10); } } }

这段代码把映射逻辑直接摆出来:鼠标左键按下对应手指 down,鼠标移动对应手指 move,松开对应手指 up。参数上要注意三件事:一是消息循环不能被阻塞,否则GetAsyncKeyState会漏状态;二是在 DPI 缩放比例不是 100% 的机器上,GetCursorPos返回的是物理像素坐标,和注入接口一致,不要自作主张除以缩放比;三是循环节流 10 毫秒即可,太快会注入大量冗余 move,给应用层手势识别带来压力。

4. 把它做成开机可用的 Win10 模拟触摸驱动:计划任务与双保险

4.1 为什么不用 sc.exe 服务:会话隔离问题

有同学拿到上面的代码后第一反应是“做成 Windows 服务,开机自启”。这里有一个很常见的坑:InjectTouchInput只能在交互式桌面会话里工作。如果把程序注册成LocalSystem服务,它运行在 Session 0,Win10 会认为“这不是人所在的桌面”,触摸注入会被拒绝或静默丢弃。解决的办法是把它做成“登录后自启的后台程序”,而不是真正意义上的系统服务。很多声称 win10 可用的模拟触摸驱动,交付物其实就是计划任务 + 常驻 exe,对外叫“驱动”,对内是一个用户态守护进程。

4.2 登录自启:计划任务 + 最高权限

以下 PowerShell 脚本把编译好的SimTouchDriver.exe复制到 Program Files,并注册一个“用户登录时启动”的计划任务:

$dest = "$env:ProgramFiles\SimTouchDriver" New-Item -ItemType Directory -Force -Path $dest | Out-Null Copy-Item -Path ".\SimTouchDriver.exe" -Destination $dest -Force $taskName = "SimTouchDriver" $action = New-ScheduledTaskAction -Execute "$dest\SimTouchDriver.exe" -WorkingDirectory $dest $trigger = New-ScheduledTaskTrigger -AtLogOn $principal = New-ScheduledTaskPrincipal -GroupId "S-1-5-32-545" -LogonType Interactive -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 0) Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force

这里的关键参数是-LogonType Interactive,它保证任务在用户登录后的桌面会话中启动;-RunLevel Highest让程序获得管理员令牌。很多 Win10 应用以管理员权限运行,如果你的注入器不是管理员,InjectTouchInput注入的事件可能到达不了那些高权限窗口。-ExecutionTimeLimit (New-TimeSpan -Minutes 0)表示不限制运行时长,否则计划任务默认会在 72 小时后杀掉你的后台线程。

计划任务还有一个好处:进程被杀后会按设置自动重启。你可以在 New-ScheduledTaskSettingsSet 里追加-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1),让异常崩溃时有一分钟级的自愈能力。这比 Run 注册表项可靠得多。

4.3 卸载与交付:一套干净的脚本

卸载时不要只删文件,否则计划任务会残留指向空路径,导致事件日志刷错误。卸载脚本如下:

$taskName = "SimTouchDriver" Unregister-ScheduledTask -TaskName $taskName -Confirm:$false Remove-Item -Path "$env:ProgramFiles\SimTouchDriver" -Recurse -Force

交付时还建议在 exe 里做单例互斥锁,避免用户登录多次或者任务重复触发时拉起多个实例。多个实例同时注入pointerId=0,会造成系统里出现两根“手指”互相打架。我会在Main开头加一句:

using (var mutex = new Mutex(true, "Global\\SimTouchDriver")) { if (!mutex.WaitOne(TimeSpan.Zero, out bool createdNew) || !createdNew) { Console.WriteLine("SimTouchDriver already running"); return; } // 主循环 }

Global前缀让所有用户会话共享同一个互斥锁名,避免 A 用户登录后任务又为 B 用户启动一份。到这里,整个“驱动”已经从代码变成了一个 Win10 开机可用的安装包雏形:安装脚本、自启任务、单例守护、卸载清理四个环节都有了。

5. 避坑:Win10 上模拟触摸驱动常见的 5 个翻车点

5.1 注入没有效果,应用只收到鼠标消息

现象:程序跑起来没有任何报错,画图板能用鼠标画,但目标手势应用完全不识别。

原因:最常见的是注入程序和目标应用权限级别不一致。Win10 从 1607 开始强化 UIPI,低完整性进程不能向高完整性窗口注入输入。比如目标应用以管理员身份运行,而你的注入器只是普通权限,InjectTouchInput虽然返回成功,事件也会在窗口消息层被丢弃。

解决:统一权限。把注入器进程通过计划任务的“使用最高权限运行”提升到管理员,目标应用同样以管理员运行。如果目标应用故意降权到低完整性,那注入器反而要对应降权,这种事在自动化测试平台里很常见,先用Process Explorer看两个进程的 Integrity Level 是否一致。

5.2 InjectTouchInput 返回 false,错误码 5 或 87

现象:调用InitializeTouchInjection成功,但InjectTouchInput返回 false,Marshal.GetLastWin32Error()给出 5(拒绝访问)或 87(参数错误)。

原因:错误码 5 通常是权限问题或会话不对;错误码 87 多数是结构体布局不对或字段非法。这个 API 对结构体长度非常敏感,如果用 32 位进程在 64 位系统上跑,POINTER_INFO里IntPtr长度不同,结构体整体长度差别很大,系统直接判定参数非法。另一个常见原因是pressure填了超过 1024 的值,或者rcContact的 left/top 超出屏幕范围。

解决:先确认平台目标为 x64;其次在调试时对每个字段显式初始化,不要依赖默认值;pressure固定不超过 1024;rcContact要和当前屏幕边界做一次 clamp。如果还报 87,把POINTER_TOUCH_INFO结构体逐字段输出,检查sourceDevice和hwndTarget是否为 0,这两个句柄不能被填入随机指针。

5.3 手指抬起后还粘在屏幕上

现象:执行 TouchUp 后,手势应用里触点仍然停留在屏幕,后续操作全部乱套。

原因:TouchUp 的pointerFlags只给了POINTER_FLAG_UP,没有同时清掉POINTER_FLAG_INRANGE;或者 up 事件里ptPixelLocation传了 0,0,系统认为触点瞬间飞到左上角并还残留按压。另一个隐藏原因是同一个pointerId在 down 和 up 之间不一致,系统无法配对抬起事件。

解决:TouchUp 时仍传入最后一次坐标,并且不要带INRANGE标志。我在生产环境里还会强制把touchFlags设为 0,把pressure设为 0,让系统明确感知到“这根手指不再接触屏幕”。

5.4 开机自启失败,或者偶尔不生效

现象:安装后第一次手动运行正常,重启电脑后任务在后台却没有任何触摸响应。

原因:计划任务被 Win10 的“启动应用”列表或安全软件拦截;或者任务触发时程序立即尝试注入,但 explorer 还未完全初始化,桌面会话尚不可用。还有可能是你用了 Run 注册表项而没有设置“以管理员身份运行”,导致权限降级后注入失败。

解决:把计划任务的登录触发器改成“延迟 30 秒启动”,在主循环里加一个重试逻辑:如果InitializeTouchInjection返回 false,等待 3 秒再尝试,直到成功。不要一失败就退出。配合任务计划程序里的“如果任务失败,重新每 1 分钟启动一次”,基本能覆盖大多数重启场景。

5.5 远程桌面和虚拟机里失效

现象:在本地 Win10 上一切正常,一旦通过远程桌面连接或在 VMware Workstation 里运行,注入坐标对不上或不触发。

原因:远程桌面会话会走独立的图形输入会话,触摸注入 API 在 RDP 重定向链路上不是所有版本都支持;虚拟机里如果没有安装增强工具,触控屏 HID 设备缺失,但InjectTouchInput不依赖 HID 设备,所以更多是显卡/会话层问题。Win10 的 RDP 客户端和主机之间还有 DPI 缩放协商,注入坐标被双方各缩放一次,最终落点偏掉。

解决:Drop 掉远程桌面作为验证环境,用真实本地控制台会话做基准。虚拟机里先把 VMware Tools 或对应虚拟化增强工具装好,并在虚拟机设置中加入“平板电脑”输入设备;如果只是做自动化测试,建议直接用 Windows 沙盒或物理机,省去会话层干扰。

6. 验证心法:用 WPF 触控回调看见注入的触点

6.1 一个 30 行验证窗口

调试模拟触摸驱动时,最怕的是“注入成功但应用没反应”。我会写一个 WPF 验证窗口,直接接收触摸消息并显示触点坐标。这个窗口本身不处理手势,只看原始触点路径。

protected override void OnPreviewTouchDown(TouchEventArgs e) { foreach (TouchPoint p in e.GetIntermediateTouchPoints(this)) { Title = $"TouchDown: {p.Position}"; this.Background = Brushes.LightGreen; } base.OnPreviewTouchDown(e); } protected override void OnPreviewTouchMove(TouchEventArgs e) { foreach (TouchPoint p in e.GetIntermediateTouchPoints(this)) { Title = $"TouchMove: {p.Position}"; } base.OnPreviewTouchMove(e); } protected override void OnPreviewTouchUp(TouchEventArgs e) { Title = "TouchUp"; this.Background = Brushes.White; base.OnPreviewTouchUp(e); }

把上面代码放在一个空白的 WPF 窗口里,运行注入器,如果标题栏实时变化,说明触摸链路是通的;如果只有鼠标事件,说明注入根本没有进入触摸消息流。这个验证方法比用 WinDbg 跟踪内核快得多,也是我每次做模拟驱动时的第一道回归门。

6.2 参数微调:接触面积、压力与方向角

验证窗口能看到坐标后,再回头调参数。Win10 的手势识别器对rcContact非常敏感,接触面积越小越接近“指尖点击”,越大越像“手掌误触”。单指点击时我习惯把接触区域设为边长 30 像素的正方形;做双指缩放时,两根触点的接触区域要避免重叠,否则系统可能合并触点。orientation字段在 Win10 的 Inking 场景下有特殊含义,手写应用会参考它判断笔锋方向;鼠标映射触摸场景填 0 就行,不要随机填 90。pressure在高版本 Win10 中影响笔迹粗细,触摸手势一般取满值 1024 最稳。

6.3 我的教训与习惯

我在 Win10 22H2 上调试时,最深的教训是不要迷信“注入成功”这四个字。InjectTouchInput返回 true 只代表系统接受了数据,不代表应用手势层认了。后来我养成了一个习惯:所有模拟触摸驱动都先跑验证窗口,再跑目标应用,最后才加开机自启。现在做新项目时,还会顺手把目标软件是否要求WM_POINTER还是WM_TOUCH写进测试用例,因为这两个事件在 Win10 上虽然常同时产生,但少数框架只监听其中一种。希望这些经验和踩坑记录能帮你把模拟触摸驱动从“能跑”做到“能交付”。

本文还有配套的精品资源,点击获取

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

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

立即咨询