From 42cee3c87164e9dbe95742d05d5c7be33905652b Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sun, 5 Jul 2026 20:20:11 -0500 Subject: [PATCH] Fix: guard Win2D draw callbacks so a render exception can't crash the app A CanvasControl draw callback that throws is re-raised by WinUI as a stowed exception (0xc000027b) that fail-fasts the process and bypasses the managed UnhandledException handler. Transient out-of-range layout/geometry (e.g. GetCharacterRegions throwing E_INVALIDARG when a rebuilt layout and a computed index momentarily disagree) could therefore hard-crash the host app. Wrap each of the four Canvas_*_Draw handlers in try/catch + Debug.WriteLine so the worst case is one skipped frame instead of a crash. CanvasDrawed() tracking stays outside the try. --- .../Core/CoreTextControlBox.xaml.cs | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/TextControlBox/Core/CoreTextControlBox.xaml.cs b/TextControlBox/Core/CoreTextControlBox.xaml.cs index 0e26204..f644e68 100644 --- a/TextControlBox/Core/CoreTextControlBox.xaml.cs +++ b/TextControlBox/Core/CoreTextControlBox.xaml.cs @@ -509,30 +509,66 @@ private void UserControl_PointerExited(object sender, PointerRoutedEventArgs e) } //Canvas event + // A Win2D draw callback runs inside the composition/render pipeline. If it throws, WinUI + // re-raises the exception as a stowed exception (0xc000027b) that fail-fasts the whole + // process and bypasses the managed UnhandledException handler, so it cannot be caught by + // the app. Transient out-of-range layout/geometry can occur while rendering very large or + // rapidly-changing content (e.g. the rebuilt text layout and a computed selection/slice + // index momentarily disagree and CanvasTextLayout.GetCharacterRegions throws E_INVALIDARG). + // Guarding each draw handler turns the worst case into a single skipped frame (the canvas + // redraws on the next invalidation) instead of a crash. private void Canvas_Text_Draw(CanvasControl sender, CanvasDrawEventArgs args) { - textRenderer.Draw(sender, args); + try + { + textRenderer.Draw(sender, args); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"TextControlBox: Canvas_Text_Draw failed: {ex}"); + } initializationManager.CanvasDrawed(0); } private void Canvas_Selection_Draw(CanvasControl sender, CanvasDrawEventArgs args) { - selectionRenderer.Draw(sender, args); + try + { + selectionRenderer.Draw(sender, args); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"TextControlBox: Canvas_Selection_Draw failed: {ex}"); + } initializationManager.CanvasDrawed(1); } private void Canvas_Cursor_Draw(CanvasControl sender, CanvasDrawEventArgs args) { - cursorRenderer.Draw(Canvas_Text, Canvas_Cursor, args); + try + { + cursorRenderer.Draw(Canvas_Text, Canvas_Cursor, args); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"TextControlBox: Canvas_Cursor_Draw failed: {ex}"); + } initializationManager.CanvasDrawed(2); } private void Canvas_LineNumber_Draw(CanvasControl sender, CanvasDrawEventArgs args) { - if (!lineNumberManager._ShowLineNumbers) + try { - lineNumberRenderer.HideLineNumbers(sender, lineNumberManager._SpaceBetweenLineNumberAndText); - return; - } + if (!lineNumberManager._ShowLineNumbers) + { + lineNumberRenderer.HideLineNumbers(sender, lineNumberManager._SpaceBetweenLineNumberAndText); + return; + } - lineNumberRenderer.Draw(Canvas_LineNumber, args, lineNumberManager._SpaceBetweenLineNumberAndText); + lineNumberRenderer.Draw(Canvas_LineNumber, args, lineNumberManager._SpaceBetweenLineNumberAndText); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"TextControlBox: Canvas_LineNumber_Draw failed: {ex}"); + } } //Focus: