本文将和大家介绍专为 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. 天天用defineEmits宏函数,竟然不知道编译后是vue2的选项式API?

    前言 我们每天都在使用 defineEmits 宏函数,但是你知道defineEmits 宏函数经过编译后其实就是vue2的选项式API吗?通过回答下面两个问题,我将逐步为你揭秘defineEmits ...

  2. echarts中实现多个label

    先来个效果图 如果你刚好需要实现这种效果,那么可以瞅一瞅了 我要开始水文了 如图所示,图中顶部部分文字乍一看好像是独立于柱状图,显示在最顶上,其实它也是由柱状图模拟而成. 只是吧图形相关属性做了隐藏, ...

  3. 使用maven命令 创建基于Scala的flink项目

    windows下 mvn archetype:generate ^ -DarchetypeGroupId=org.apache.flink ^ -DarchetypeArtifactId=flink- ...

  4. DenseBox:思想超前的早期Anchor-free研究 | CVPR 2015

    DenseBox检测算法的设计十分超前,如今很多Anchor-free方法有其影子,如果当时不是比Faster R-CNN晚了一点出现,可能目标检测领域很早就开始往Anchor-free的方向发展了 ...

  5. Scala 类和对象与Java的对比

    一.包 1 package com{ 2 3 import com.atguigu.scala.Inner 4 5 // 在外层包中定义单例对象 6 object Outer{ 7 var out: ...

  6. 汇编语言-int指令

    int 指令 int 指令的格式为:int n,n为中断类型码,它的功能是引发终端过程. CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程如下. 取中断类型码n: 标志寄存器入栈, ...

  7. 详解Java Chassis 3与Spring Cloud的互操作

    本文分享自华为云社区<Java Chassis 3技术解密:与Spring Cloud的互操作>,作者: liubao68. Java Chassis 3一个很重要的设计原则:利用架构的韧 ...

  8. 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(3)

    1.问题描述: 调用HarmonyOS API发送通知,能查到状态是送达终端设备,但是终端设备上没收到通知卡片. 解决方案: 通知应用大图标不能超过30kb,通知参数限制,参考如下:https://g ...

  9. HarmonyOS:NativeWindow 开发指导

      场景介绍 NativeWindow是HarmonyOS本地平台化窗口,表示图形队列的生产者端.开发者可以通过NativeWindow接口进行申请和提交Buffer,配置Buffer属性信息. 针对 ...

  10. HarmonyOS非线性容器特性及使用场景

      非线性容器实现能快速查找的数据结构,其底层通过hash或者红黑树实现,包括HashMap.HashSet.TreeMap.TreeSet.LightWeightMap.LightWeightSet ...