通過解讀WPF觸控原始碼,分析WPF插拔裝置觸控失效的問題(問題篇)
在 .NET Framework 4.7 以前,WPF 程式的觸控處理是基於作業系統元件但又自成一套的,這其實也為其各種各樣的觸控失效問題埋下了伏筆。再加上它出現得比較早,觸控失效問題也變得更加難以解決。即便是 .NET Framework 4.7 以後也需要開發者手動開啟 Pointer
訊息,並且存在相容性問題。
本文將通過解讀 WPF 觸控部分的原始碼,分析 WPF 插拔裝置觸控失效的問題。隨後,會給微軟報這個 Bug。
本文使用多種語言編寫,請選擇適合你閱讀的語言:
- 通過解讀 WPF 觸控原始碼,分析 WPF 插拔裝置觸控失效的問題(問題篇)
- WPF Applications Stop Responding to Touches after Adding or Removing Tablet Devices
所謂“觸控失效”,指的是無論你如何使用手指或觸控筆在觸控式螢幕上書寫、互動,程式都沒有任何反應。而使用滑鼠操作則能正常使用。
-
本文所述的“觸控失效問題”我在 WPF 程式無法觸控操作 一文中有所提及,但本文偏向於分析其內部發生的原因。
-
本文與 林德熙 的 WPF 插拔觸控裝置觸控失效 所述的是同一個問題。那篇文章會更多的偏向於原始碼解讀,而本文更多地偏向於分析觸控失效的過程。
WPF 程式插拔裝置導致觸控失效問題
無論你寫的 WPF 程式多麼簡單,哪怕只有一個最簡單的視窗帶著一個可以互動的按鈕,本文所述的觸控失效問題你都可能遇到。
具體需要的條件為:
- 執行 任意的 WPF 程式
- 插拔帶有觸控的 HID 裝置(可以是物理插拔,也可以是驅動或軟體層面的插拔)
以上雖說是必要條件,但如果要提高觸控失效的復現概率,需要製造一個較高的 CPU 佔用:
- 當前系統中有 較高的 CPU 佔用率
可能還有一些尚不確定的條件:
- 是否對 .NET Framework 的版本有要求?
- 是否對 Windows 作業系統的版本有要求?
將以上所有條件組合起來,對於觸控失效的問題描述為:
- 當執行任意的 WPF 程式時,如果此時作業系統有較高的 CPU 佔用,並且此時存在帶有觸控的 HID 裝置插拔,那麼此 WPF 程式可能出現“觸控失效”問題,即此後此程式再也無法觸控操作了。
- 如果此時系統中同時執行了多個 WPF 程式,多個 WPF 程式可能都會在此時出現觸控失效問題。
觸控失效原因初步分析
WPF 從收集裝置觸控到大多數開發者所熟知的 Stylus
和 Mouse
事件需要兩個不同的執行緒完成。
- 主執行緒,負責進行 Windows 訊息迴圈
- StylusInput 執行緒,負責從 WPF 非託管程式碼和 COM 元件中獲得觸控資訊
主執行緒中的 Windows 訊息迴圈處理這些訊息:
- LBUTTONDOWN, LBUTTONUP
- DEVICECHANGE, TABLETADDED, TABLETREMOVED
Stylus Input 執行緒主要由 PenThreadWorker
類建立,線上程迴圈中使用 GetPenEvent
和 GetPenEventMultiple
這兩個函式來獲取整個觸控裝置中的觸控事件,並將觸控的原始資訊向 WPF 的其他觸控處理模組傳遞。傳遞的其中一個模組是 WorkerOperationGetTabletsInfo
類,其的 OnDoWork
方法中會通過 COM 元件獲取觸控裝置個數。
而導致觸控失效的錯誤程式碼就發生在以上 Stylus Input 執行緒的處理中。
-
PenThreadWorker
的GetPenEventMultiple
方法傳入的_handles
為空陣列,這會導致進行無限的等待。 -
WorkerOperationGetTabletsInfo
的OnDoWork
因為 COM 元件錯誤出現COMException
或因為執行緒安全問題出現ArgumentException
;此時方法內部會catch
然後返回空陣列,這使得即時存在觸控裝置也會因此而識別為不存在。
為了方便理解以上的兩個 Bug,可以看看我簡化後的 .NET Framework 原始碼:
// PenThreadWorker.ThreadProc
while(這裡是兩層迴圈,簡化成一個以便理解)
{
// 以下的 break 都只退出一層迴圈而已。
if (this._handles.Length == 1)
{
if (!GetPenEvent(this._handles[0], 其他引數))
{
break;
}
}
else if (!GetPenEventMultiple(this._handles, 其他引數))
{
break;
}
// 後續邏輯。
}
// WorkerOperationGetTabletsInfo.OnDoWork
try
{
_tabletDeviceInfo = PenThreadWorker.GetTabletInfoHelper(pimcTablet);
}
catch(COMException)
{
_tabletDevicesInfo = new TabletDeviceInfo[0];
}
catch(ArgumentException)
{
_tabletDevicesInfo = new TabletDeviceInfo[0];
}
// 其他異常。
以上的問題分析中,ArgumentException
異常幾乎可以肯定是執行緒安全問題所致;COMException
不能確定;而 GetPenEventMultiple
中的引數 handles
實際上是用來進行非託管和託管程式碼執行緒同步用的 ResetEvent
集合,所以實際上也是執行緒同步問題導致的死鎖。
同時聯絡以上必要復現步驟中,如果當前存在高 CPU 佔用則可以大大提高復現概率;我們幾乎可以推斷,此問題是 WPF 對觸控的處理存線上程安全的隱患所致。
此觸控失效問題的解決方法
在推斷出初步原因後,根本的解決方法其實只剩下兩個了:
- 修復 WPF 的 Bug
- 由於我們無法編譯 .NET Framework 的原始碼,所以幾乎只能由微軟來修復這個 Bug,即需要新版本的 WPF 來解決這個執行緒安全隱患
- 當然,此問題的修復可以跟隨 .NET Framework 更新,也可以跟隨即將推出的 .NET Core 3 進行更新。
- 更新 Windows(傳說中的補丁)
- 新的 Windows 提供給 WPF 的 COM 元件可能也需要修復執行緒安全或其他與觸控硬體相關的問題
比較徹底的方案是以上兩者都需要修復,但都 只能由微軟來完成。
那我們非微軟開發者可以做些什麼呢?
- 降低 CPU 佔用率
- 雖然這不由我們控制,不過我們如果能降低一些意料之外的高 CPU 佔用,則可以大幅降低 WPF 觸控失效問題出現的概率。
然而作為使用者又可以做些什麼呢?
- 重新插拔觸控裝置(如果你的觸控框是通過 USB 連線可以手工插拔的話)
觸控失效問題的分析過程
以上結論的得出,離不開對 .NET Framework 原始碼的解讀和除錯。
由於 WPF 的觸控原理涉及到較多型別和原始碼,需要大量篇幅描述,所以不在本文中說明。閱讀以下文章可以更加深入地瞭解這個觸控失效的問題:
本文所有的 .NET Framework 原始碼均由 dnSpy 反編譯得出,分析過程也基本是藉助 dnSpy 的無 pdb 除錯特性進行。關於 dnSpy 的更多使用,可以閱讀:
相關文章
- WPF 已知問題 監聽 WMI 事件導致觸控失效事件
- WPF --- 觸控式螢幕下的兩個問題
- wpf 觸控 觸控後無法開啟pupup
- WPF Command CanExecute 觸發一次的問題
- 記錄一下 WPF 視窗 觸控失效 的一種場景
- 解決WPF中過載Window.OnRender函式失效問題函式
- dotnet 讀 WPF 原始碼筆記 渲染收集是如何觸發原始碼筆記
- 閱讀APP原始碼,瞭解Android studio觸控事件,切換圖片APP原始碼Android事件
- WPF進階技巧和實戰09-事件(2-多點觸控)事件
- 通訊錄觸控下拉demo
- WPF 解決 CommandParameter 引數不更新問題
- Unity觸控式螢幕觸控事件定義Unity事件
- mvvm模式 事件觸發器[wpf]MVVM模式事件觸發器
- 低功耗抗干擾2路觸控檢測晶片,2通道2鍵觸控觸控IC-VK3702DM晶片
- 開發觸控式螢幕的頁面時應該注意些什麼問題?
- 高抗干擾 低功耗VK36E4-4通道/四觸控觸控晶片,4按鍵觸控檢測晶片晶片
- 最新資訊:iPhone 11顯示模組更換計劃,用於解決觸控問題iPhone
- ScrollView 觸控事件View事件
- 觸控檯燈
- JS觸控事件JS事件
- 初次接觸React Native遇到的問題React Native
- WPF TreeView IsExpanded 繫結不上的問題View
- WPF 從裸 Win 32 的 WM_Pointer 訊息獲取觸控點繪製筆跡
- PLC與觸控式螢幕通訊
- wpf win7相容問題Win7
- WPF頻繁更新UI卡頓問題UI
- Mac如何防止觸控板誤觸?關閉MacBook系列筆記本觸控板的方法Mac筆記
- 筆記本觸控板手勢大全 觸控板快捷手勢筆記
- 新鮮出爐:appium2.0+ 單點觸控和多點觸控新的解決方案APP
- wpf popup導致MouseLeftButtonUp無法觸發
- 支付寶支付回撥觸發問題
- window.onload 觸發時機問題
- 觸控式螢幕響應速度效能分析
- @Value失效的問題
- touch事件和click事件多次觸發的問題事件
- CheckBox、ToggleSwitch 程式碼修改 自動觸發 onclick的問題,把onclick的邏輯 寫在onmouseup裡 解決問題
- 透過迴圈引用問題來分析Spring原始碼Spring原始碼
- archlinux 觸控板手勢配置Linux