本文将和大家介绍专为 WPF 触摸模块提供的 ITabletManager 的 GetTabletCount 方法在 Windows 11 系统的底层实现

本文属于 WPF 触摸相关系列博客,偏系统底层介绍,更多触摸博客请看 WPF 触摸相关

大家都知道在 Windows 7 系统,有专门的笔和触摸服务提供触摸消息的支持。而 WPF 是从 Vista 年代就开始的框架,自然需要支持到 XP 系统。在 XP 系统里面,还没有完善的 WM_Touch 消息,同时又需要兼顾性能,最好走的是 RealTimeStylus 这一套。在 Windows 下有一套专门给 WPF 触摸模块使用 COM 接口,这一套接口提供了和 RealTimeStylus 几乎一样的实现功能,详细请看 https://learn.microsoft.com/en-us/windows/win32/tablet/com-apis-used-by-windows-presentation-foundation

更多关于这一个 COM 触摸层的介绍,请看 WPF 用到的触摸的 COM 接口

如果对这一个 COM 触摸层在 WPF 里的对接感兴趣,请参阅 WPF 触摸底层 PenImc 是如何工作的

但是从 Win10 开始,系统里面就没有了专门的笔和触摸服务,而是将触摸消息集成到系统里面

本文就来和大家聊聊在 Windows 11 下的 WPF 的触摸底层,也就是 ITabletManager 接口是定义在哪里,以及里面的 GetTabletCount 方法是如何实现

由于各个系统都可以对此进行更改,本文着重在于编写调试用的代码,在 VisualStudio 和 IDA 的辅助下了解在 Windows 11 22H2 22621 上的实现

为了了解 ITabletManager 的具体实现 DLL 在哪,可以定义出 COM 接口,通过拿到 COM 接口的虚函数表地址从而了解到对应的 DLL 文件

先编写定义 ITabletManager 接口的代码,代码如下

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using HRESULT = System.Int32; [ComImport, Guid("764DE8AA-1867-47C1-8F6A-122445ABD89A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITabletManager
{
int GetDefaultTablet(out ITablet ppTablet);
int GetTabletCount(out ulong pcTablets);
int GetTablet(ulong iTablet, out ITablet ppTablet);
}

以上的 ITablet 接口不是本文的重点,咱只需要定义空接口即可,不需要定义里面的方法

[ComImport, Guid("1CB2EFC3-ABC7-4172-8FCB-3BC9CB93E29F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITablet //: IUnknown
{
}

接着在代码里面,通过如文档所述方法,先创建 CLSID_TabletManagerS 对象,再将其转换为 ITabletManager 接口

Call CoCreateInstance with a class ID of CLSID_TabletManagerS, and then call QueryInterface to get a pointer to the ITabletManager Interface. The CLSID_TabletManagerS GUID is defined as follows: #define CLSID_TabletManagerS uuid(A5B020FD-E04B-4e67-B65A-E7DEED25B2CF)

以上文档对应的 C# 代码如下

            var typeFromClsid = Type.GetTypeFromCLSID(new Guid("A5B020FD-E04B-4e67-B65A-E7DEED25B2CF"));
object comObject = Activator.CreateInstance(typeFromClsid); var manager = comObject as ITabletManager;
manager!.GetTabletCount(out var tabletCount);

开启本机调试,运行代码,在以上的代码的最后一句话下断点,进入断点之后即可展开 comObject 的本机视图,找到 COM 对象的 __vfptr 地址。再根据地址从 VisualStudio 的调试模块里面找到落在其中的地址范围内的 DLL 文件。如下图

在写到这里我才看到 VisualStudio 里已经写了 wisp.dll 文件了,不需要自己去算地址,也是方便哈

了解到了现在的 ITabletManager 是定义在 C:\Windows\System32\wisp.dll 文件,即可将此文件丢到 IDA 里面反编译一下,如下图

可以看到在第 53 行里使用的是 GetPointerDevices 方法。我感觉这就是核心实现了,这个 GetPointerDevices 是在 Win10 下的 WM_Pointer 触摸系列下的获取触摸设备数量的方法

也就是说 ITabletManager 的 GetTabletCount 的核心实现又到 POINTER 机制里面了。这就超过了本文的范围了哈,不过能够知道 ITabletManager 的 GetTabletCount 底层也是到 POINTER 机制也就足够我玩的。因为这侧面反映了 Win11 不是保留旧代码,而是 API 重定向和加上兼容的代码而已。换句话说,如果有一个 bug 是 Pointer 层存在的,那么 WPF 的 COM 触摸层也会存在。但反过来不成立,如果有某个是 bug 是在 WPF 的 COM 触摸层存在的,可能是因为 Win11 的 API 调用或兼容代码挖的坑,不一定是 Pointer 的问题

关于 GetPointerDevices 的描述,请参阅 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpointerdevices

简单的 GetPointerDevices 用法可以使用 PInvoke 调用,如下面例子

先安装 Microsoft.Windows.CsWin32 库,如 dotnet 使用 CsWin32 库简化 Win32 函数调用逻辑 博客提供的方法

接下来编写代码从 GetPointerDevices 里获取触摸信息

                StringBuilder stringBuilder = ...

                // 获取 Pointer 设备数量
uint deviceCount = 0;
PInvoke.GetPointerDevices(ref deviceCount,
(Windows.Win32.UI.Controls.POINTER_DEVICE_INFO*)IntPtr.Zero);
Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[] pointerDeviceInfo =
new Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[deviceCount];
fixed (Windows.Win32.UI.Controls.POINTER_DEVICE_INFO* pDeviceInfo = &pointerDeviceInfo[0])
{
// 这里需要拿两次,第一次获取数量,第二次获取信息
PInvoke.GetPointerDevices(ref deviceCount, pDeviceInfo);
stringBuilder.AppendLine($"PointerDeviceCount:{deviceCount} 设备列表:");
foreach (var info in pointerDeviceInfo)
{
stringBuilder.AppendLine($" - {info.productString}");
}
}

需要调用 GetPointerDevices 两次,第一个获取数量,第二次获取信息。这个 GetPointerDevices 在第一个参数传入是 0 的时候,是不会填充第二个参数数组信息

以上就是专为 WPF 触摸模块提供的 ITabletManager 的 GetTabletCount 方法在 Windows 11 系统的底层实现

探索 WPF 的 ITabletManager.GetTabletCount 在 Win11 系统的底层实现的更多相关文章

  1. 探索ASP.NET MVC框架之路由系统

    引言 对于ASP.NET MVC的路由系统相信大家肯定不陌生.今天我们就深入ASP.NET的框架内部来看一下路由系统到底是怎么通过我们给出的地址(例如:/Home/Index)解析出Controlle ...

  2. 【WPF】使用 XAML 的 Trigger 系统实现三态按钮

    利用 WPF 的 Trigger 系统,也可以很简单的只使用xmal实现三态按钮.在Window或UserControl的资源中声明按钮的style并加入触发功能.使用的时候直接在button里复写s ...

  3. 【.NET6+WPF+Avalonia】开发支持跨平台的WPF应用程序以及基于ubuntu系统的演示

    前言:随着跨平台越来越流行,.net core支持跨平台至今也有好几年的光景了.但是目前基于.net的跨平台,大多数还是在使用B/S架构的跨平台上:至于C/S架构,大部分人可能会选择QT进行开发,或者 ...

  4. 千姿百态,瞬息万变,Win11系统NeoVim打造全能/全栈编辑器(前端/Css/Js/Vue/Golang/Ruby/ChatGpt)

    我曾经多次向人推荐Vim,其热情程度有些类似现在卖保险的,有的时候,人们会因为一些弥足珍贵的美好暗暗渴望一个巨大的负面,比如因为想重温手动挡的快乐而渴望买下一辆二十万公里的老爷车,比如因为所谓完美的音 ...

  5. WPF应用程序最小化到系统托盘

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows; u ...

  6. Parallels Desktop 18(Mac虚拟机)v18.0.0(53049)无限试用版+win11系统

    Parallels Desktop 18 for Mac 是一款强大的虚拟机软件,让您无需重启即可在 Mac 上运行 Windows 应用程序不会减慢 Mac 的运行速度,具有速度快.操作简单且功能强 ...

  7. 一些vue 响应式系统的底层的细节

    当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/ ...

  8. c# WPF 设置窗口一直在其中窗口后面/底层窗口

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...

  9. WPF如何更改系统控件的默认高亮颜色 (Highlight brush)

    我们在用WPF时, 经常会对系统控件的默认高亮等等颜色进行更改. 以前通常是用controlTemplate来实现. 今天发现一个更合理或者简单的方法: 用系统默认颜色的key, 比如 SystemC ...

  10. WPF中取得系统字体列表

    原文:WPF中取得系统字体列表 在GDI+中,我们可以通过如下方式取得系统所有字体: foreach(FontFamily f in FontFamily.Families){   // 处理代码} ...

随机推荐

  1. C#通义千问apl

    1.在阿里申请和开通模型服务灵积:链接地址 2.在gitee下载代码:Gitee链接 3.在代码中更换你的API-KEY 4.注意要开通:通义千问/qwen-turbo模型 5.如图所示

  2. OpenLayers 点击显示经纬度Demo

    这里给大家分享我在OpenLayers 地图开发工作中总结出的一下代码和注意点,希望对大家有所帮助 效果如下: 核心代码展示:附带讲解注释 var map = new ol.Map({ // 初始化地 ...

  3. Nancy一个轻量级用于构建基于 HTTP 的 Web 服务

    记录一下: Nancy官网地址:http://nancyfx.org/ GitHub文档地址:https://github.com/NancyFx/Nancy/wiki/Documentation 有 ...

  4. KingbaseES查找慢查询语句和阻塞会话

    在处理数据库性能问题时,识别和分析慢查询及阻塞会话是至关重要的步骤.数据库管理员和开发人员常常需要依赖特定的工具和查询语句来追踪这些性能瓶颈. 当数据库响应变慢或出现处理延迟时,第一步通常是查找那些执 ...

  5. Unicode编码解码的全面介绍

    1. Unicode的起源和发展 Unicode是一个国际标准,旨在统一世界上所有文字的表示方式.它最初由Unicode协会创立,解决了不同字符集之间的兼容性问题.Unicode的发展经历了多个版本, ...

  6. #树形dp#洛谷 1272 重建道路

    题目 给出一个大小为 \(n\) 的树, 问至少断掉多少条边使得存在一个大小为 \(m\) 的连通块 \(n\leq 150\) 分析 设 \(dp[x][s]\) 表示以 \(x\) 为根的子树至少 ...

  7. #Tarjan#洛谷 5676 [GZOI2017]小z玩游戏

    题目 分析 可能玩两次也就是形成环即是Tarjan缩点后在同一个强连通分量 如果按照游戏连边数量将达到\(O(n^2)\),当中其实有很多边可以共用, 考虑\(i\)连向\(i\)的倍数,以及有趣程度 ...

  8. Hi3861 通过UART串口协议与其它开发板进行通信

    一.搭建编译环境 1.下载虚拟机VMware和Ubuntu20.0.14 下载 VMware Workstation Pro | CN https://www.vmware.com/cn/produc ...

  9. 国产Linux发行版 Deepin 评价与主观展望 我与Deepin的故事

    全文皆属于up自己的主观臆断,有不到之处可以进行批评,共同成长. P1 编辑时间(2021-2-20) 昨天,我刷b站的时候见到一则旧闻,关于我国操作系统的.原本只是想写个1000字左右的评论的,没想 ...

  10. C#-GroupBox包含控件,如何获取这些控件的名称

    您可以使用 Enumerable.OfType在GroupBox中查找和投射您的RadioButtons: var radioButtons = groupBox1.Controls.OfType&l ...