在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
本文記錄一個 UWP 或 WinUI3 的開發過程中的問題,當開發者調用 AddHandler 時,所需的 Handler 參數類型為 RoutedEventHandler 類型,然而實際上正確類型是需要與所監聽事件匹配才能符合預期工作,否則將拋出缺乏信息的參數異常
開始之前先慣例吐槽一下,我從 2015 開始開發 UWP 應用,然而到 2024 的時候,依然沒有看到開發體驗上的優化。且在 WinUI3 的技術底層設計上就存在無解問題,那就是許多錯誤只依靠 COM 的 HR 錯誤號信息,開發者難以瞭解真正意義上的調錯信息和具體的錯誤原因。比如說本文所記錄的問題
以下是最短復現問題的代碼
public MainPage()
{
this.InitializeComponent();
RoutedEventHandler handler = (_, _) =>
{
System.Diagnostics.Debug.WriteLine("PointerPressed");
};
AddHandler(PointerPressedEvent, handler, true);
}
以上代碼是能夠通過構建的,原因是 AddHandler 裡面的 Handler 參數就是 object 類型的。然而在運行中將會拋出參數異常,異常信息如下
System.ArgumentException: Value does not fall within the expected range.
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr)
異常裡面還有 HResult 是 -2147024809 的值。其實這個 -2147024809 需要使用 16 進位去看,結果是有名的 0x80070057 錯誤號。通過 Error 工具可以看到這表示的是 COM 的通用錯誤信息,名為 E_INVALIDARG 的錯誤,意思就是參數錯誤
# for hex 0x80070057 / decimal -2147024809
COR_E_ARGUMENT corerror.h
# An argument does not meet the contract of the method.
DDERR_INVALIDPARAMS ddraw.h
DIERR_INVALIDPARAM dinput.h
DSERR_INVALIDPARAM dsound.h
STIERR_INVALID_PARAM stierr.h
DRM_E_INVALIDARG windowsplayready.h
E_INVALIDARG winerror.h
# One or more arguments are invalid
# as an HRESULT: Severity: FAILURE (1), FACILITY_WIN32 (0x7), Code 0x57
# for hex 0x57 / decimal 87
ERROR_INVALID_PARAMETER winerror.h
# The parameter is incorrect.
# 8 matches found for "0x80070057"
這就是 WinUI3 的一個無解設計問題,通過 HResult 返回錯誤信息,所包含的信息量太少了,且很多時候距離實際錯誤點又十分遠。應用開發者又不知道 WinUI3 底層投了哪些毒,難以知道所說的參數錯誤具體指的是什麼錯誤。這一點也是制約了 WinUI 3 的生態,但這一點又是屬於 WinUI 3 的基礎設計的問題,預估難以更改
這一次的錯誤信息裡面在 Data 裡面還包含幾條看似沒有用,實際也沒有用的信息,分別如下
+ [0] {[Description, 不支持此介面
]} object {System.Collections.DictionaryEntry}
+ [1] {[RestrictedDescription, 不支持此介面
]} object {System.Collections.DictionaryEntry}
+ [2] {[RestrictedErrorReference, ]} object {System.Collections.DictionaryEntry}
+ [3] {[RestrictedCapabilitySid, ]} object {System.Collections.DictionaryEntry}
+ [4] {[__RestrictedErrorObjectReference, WinRT.ExceptionHelpers+__RestrictedErrorObject]} object {System.Collections.DictionaryEntry}
+ [5] {[__HasRestrictedLanguageErrorObject, False]} object {System.Collections.DictionaryEntry}
也就是描述信息裡面說的是 不支持此介面
的描述信息,合起來就是:遇到參數錯誤了,因為底層不支持參數傳進來的此介面
但是就是不告訴大家,具體錯誤的是哪個參數,且錯在哪裡了。要是能夠明白說明 handler 參數的類型不符合預期之類的,那開發者的調試效率將會高出許多
本文記錄的錯誤問題原因是 PointerPressedEvent 所對應的是 PointerEventHandler 類型,而不是 RoutedEventHandler 類型,修複的代碼如下
PointerEventHandler handler = (_, _) =>
{
System.Diagnostics.Debug.WriteLine("PointerPressed");
};
AddHandler(PointerPressedEvent, handler, true);
那日常開發過程中,如何知道 AddHandler 裡面的 handler 參數應該傳入什麼類型的委托呢?其實方法很簡單,只需要使用對應的事件,看看對應的事件定義是什麼。比如 PointerPressedEvent 對應的就是 PointerPressed 事件,按照通用命名法就是對應的事件就是對應路由事件定義去掉 Event 尾碼。通過查閱文檔或者是在 VisualStudio 裡面點點看,就可以看到對應的事件的定義,如下麵代碼就是 PointerPressed 的定義,可以看到事件是 PointerEventHandler 類型的委托
public event PointerEventHandler PointerPressed { add; remove; }
通過此方式即可知道傳入 AddHandler 的 handler 應該使用什麼樣的類型,解決運行時失敗的原因。常見的錯誤都在於更改代碼的時候,忘記同步更改對應的委托類型
額外補充一點,以上的代碼的 handler 局部變數是安全的,不會被回收,原因是雖然在以上代碼裡面看起來 handler 局部變數沒被引用,然而在 AddHandler 底層裡面已經做好了引用,不會導致 handler 被回收,從而導致 COM 層訪問被回收的記憶體而炸掉的問題。但是此問題在古老的 UWP 是存在的。一個推薦的優化方法就是將 handler 存放在欄位裡面,手動防止被回收
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接著使用命令行 cd 命令進入此空文件夾,在命令行裡面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d43a62536b449ef337160f9931265a0db482ed12
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin d43a62536b449ef337160f9931265a0db482ed12
獲取代碼之後,進入 FelawchechadaGeqedaihallnela 文件夾,即可獲取到源代碼
博客園博客只做備份,博客發佈就不再更新,如果想看最新博客,請訪問 https://blog.lindexi.com/
如圖片看不見,請在瀏覽器開啟不安全http內容相容
本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名[林德熙](https://www.cnblogs.com/lindexi)(包含鏈接:https://www.cnblogs.com/lindexi ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我[聯繫](mailto:[email protected])。