翼度科技»论坛 编程开发 .net 查看内容

探索 WPF 的 ITabletManager.GetTabletCount 在 Win11 系统的底层实现

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
本文将和大家介绍专为 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
但是从 Win10 开始,系统里面就没有了专门的笔和触摸服务,而是将触摸消息集成到系统里面
本文就来和大家聊聊在 Windows 11 下的 WPF 的触摸底层,也就是 ITabletManager 接口是定义在哪里,以及里面的 GetTabletCount 方法是如何实现
由于各个系统都可以对此进行更改,本文着重在于编写调试用的代码,在 VisualStudio 和 IDA 的辅助下了解在 Windows 11 22H2 22621 上的实现
为了了解 ITabletManager 的具体实现 DLL 在哪,可以定义出 COM 接口,通过拿到 COM 接口的虚函数表地址从而了解到对应的 DLL 文件
先编写定义 ITabletManager 接口的代码,代码如下
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using System.Runtime.InteropServices;
  4. using HRESULT = System.Int32;
  5. [ComImport, Guid("764DE8AA-1867-47C1-8F6A-122445ABD89A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  6. public interface ITabletManager
  7. {
  8.     int GetDefaultTablet(out ITablet ppTablet);
  9.     int GetTabletCount(out ulong pcTablets);
  10.     int GetTablet(ulong iTablet, out ITablet ppTablet);
  11. }
复制代码
以上的 ITablet 接口不是本文的重点,咱只需要定义空接口即可,不需要定义里面的方法
  1. [ComImport, Guid("1CB2EFC3-ABC7-4172-8FCB-3BC9CB93E29F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  2. public interface ITablet //: IUnknown
  3. {
  4. }
复制代码
接着在代码里面,通过如文档所述方法,先创建 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# 代码如下
  1.             var typeFromClsid = Type.GetTypeFromCLSID(new Guid("A5B020FD-E04B-4e67-B65A-E7DEED25B2CF"));
  2.             object comObject = Activator.CreateInstance(typeFromClsid);
  3.             var manager = comObject as ITabletManager;
  4.             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 里获取触摸信息
  1.                 StringBuilder stringBuilder = ...
  2.                 // 获取 Pointer 设备数量
  3.                 uint deviceCount = 0;
  4.                 PInvoke.GetPointerDevices(ref deviceCount,
  5.                     (Windows.Win32.UI.Controls.POINTER_DEVICE_INFO*)IntPtr.Zero);
  6.                 Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[] pointerDeviceInfo =
  7.                     new Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[deviceCount];
  8.                 fixed (Windows.Win32.UI.Controls.POINTER_DEVICE_INFO* pDeviceInfo = &pointerDeviceInfo[0])
  9.                 {
  10.                     // 这里需要拿两次,第一次获取数量,第二次获取信息
  11.                     PInvoke.GetPointerDevices(ref deviceCount, pDeviceInfo);
  12.                     stringBuilder.AppendLine($"PointerDeviceCount:{deviceCount} 设备列表:");
  13.                     foreach (var info in pointerDeviceInfo)
  14.                     {
  15.                         stringBuilder.AppendLine($" - {info.productString}");
  16.                     }
  17.                 }
复制代码
需要调用 GetPointerDevices 两次,第一个获取数量,第二次获取信息。这个 GetPointerDevices 在第一个参数传入是 0 的时候,是不会填充第二个参数数组信息
以上就是专为 WPF 触摸模块提供的 ITabletManager 的 GetTabletCount 方法在 Windows 11 系统的底层实现

来源:https://www.cnblogs.com/lindexi/archive/2023/09/20/17716408.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具