首页 > 其他分享 >探索 WPF 的 ITabletManager.GetTabletCount 在 Win11 系统的底层实现

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

时间:2023-09-20 09:02:25浏览次数:46  
标签:ITabletManager GetPointerDevices Windows 触摸 Win11 WPF GetTabletCount

本文将和大家介绍专为 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 接口的代码,代码如下

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 系统的底层实现

标签:ITabletManager,GetPointerDevices,Windows,触摸,Win11,WPF,GetTabletCount
From: https://www.cnblogs.com/lindexi/p/17716408.html

相关文章

  • WPF TextBlock显示固定长度字符串
    页面中TextBlock控件内容 <TextBlockx:Name="name"HorizontalAlignment="Left"Text="{BindingName,Converter={StaticResourceStringMaxLenConverter},ConverterParameter=13}"TextWrapping="NoWrap"/>设置一个转换器,并且在页面中使用:<......
  • WPF 界面或文本框焦点丢失问题
    在用户界面,有些时候需要使用键盘某个按键触发某项功能,但有时候会有焦点丢失问题发生,解决办法如下: List<T>FindVisualChild<T>(DependencyObjectobj)whereT:DependencyObject{try{List<T>list=newList<T>();......
  • WPF 踩过的坑
    1,wpf项目复制别人的图片或样式文件,生成报错,需要把文件或图片点击属性设置资源文件2,选项卡设计ui界面时,调整其它面板的控件,设置该属性IsChecked="True"<StackPanelOrientation="Horizontal"VerticalAlignment="Bottom"HorizontalAlignment="Center">......
  • 【愚公系列】2023年09月 WPF控件专题 DataGrid控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(5) -- 树列表
    在我们展示一些参考信息的时候,有所会用树形列表来展示结构信息,如对于有父子关系的多层级部门机构,以及一些常用如字典大类节点,也都可以利用树形列表的方式进行展示,本篇随笔介绍基于WPF的方式,使用TreeView来洗实现结构信息的展示,以及对它的菜单进行的设置、过滤查询等功能的实现逻辑......
  • WPF禁用Windows窗口自带的关闭按钮(非关闭拦截)
    #region禁用关闭按钮[DllImport("USER32.DLL",CharSet=CharSet.Unicode)]privatestaticexternIntPtrGetSystemMenu(IntPtrhWnd,UInt32bRevert);[DllImport("USER32.DLL",CharSet=CharSet.Unicode)]privatestaticexternUInt32RemoveMenu(......
  • WPF MVVM 循序渐进 (从基础到高级):pdf
    简介简单的三层架构示例和GLUE(胶水)代码问题第一步:最简单的MVVM示例-把后台代码移到类中第二步:添加绑定-消灭后台代码第三步:添加执行动作和“INotifyPropertyChanged”接口第四步:在ViewModel中解耦执行动作第五步:利用PRISMWPFMVVM的视频演示…内容很精彩,转了一个p......
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现Da
    在我们设计软件的很多地方,都看到需要对表格数据进行导入和导出的操作,主要是方便客户进行快速的数据处理和分享的功能,本篇随笔介绍基于WPF实现DataGrid数据的导入和导出操作。1、系统界面设计在我们实现数据的导入导出功能之前,我们在主界面需要提供给客户相关的操作按钮,如下界面......
  • WPF 添加图片,可移动图片位置
    1、创建一个.xaml文件,页面布局:<UserControlx:Class="Module.ScreentView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"......
  • 【愚公系列】2023年09月 WPF控件专题 ListView控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......