I try to hook the keyborad. Display a window when the number lock key and the caps lock key are pressed, and display the status of the current lock key in the window. When I try to click the number lock key or caps lock key quickly, it will crash in App.g.i.cs.Visual Studio display An unhandled exception of type "system. executionengenexception" occurred in Microsoft.WinUI.dll.
This is code when crash in App.g.i.cs:
static void Main(string[] args)
{
XamlCheckProcessRequirements();
global::WinRT.ComWrappersSupport.InitializeComWrappers();
global::Microsoft.UI.Xaml.Application.Start((p) => {
var context = new global::Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
global::System.Threading.SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
I think it seems that this crash happened because of the rapid change of TextBlock's Text.
This is FlyoutPage.xaml:
<Page
x:Class="MoreFlyout.Views.FlyoutPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MoreFlyout.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<AcrylicBrush x:Key="CustomAcrylicBrush" AlwaysUseFallback="False" />
</Page.Resources>
<Page.ContextFlyout>
<Flyout x:Name="FlyoutWindowContextFlyout" ShouldConstrainToRootBounds="False">
<Flyout.SystemBackdrop>
<local:AcrylicSystemBackdrop />
</Flyout.SystemBackdrop>
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Grid
x:Name="ContentArea"
MinWidth="164"
Padding="-8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<HyperlinkButton
x:Name="IconButton"
Grid.Column="0"
Width="32"
Height="32"
IsEnabled="False">
<FontIcon
x:Name="StatusFontIcon"
Margin="-4"
FontSize="12"
Foreground="White"
Glyph="" />
</HyperlinkButton>
<TextBlock
x:Name="StatusTextBlock"
Grid.Column="1"
VerticalAlignment="Center"
TextAlignment="Center" />
</Grid>
</Flyout>
</Page.ContextFlyout>
</Page>
This is FlyoutPage.xaml.cs:
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using MoreFlyout.Helpers;
using MoreFlyout.ViewModels;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace MoreFlyout.Views;
public sealed partial class FlyoutPage : Page
{
private readonly Microsoft.UI.Dispatching.DispatcherQueue dispatcherQueue;
private static System.Timers.Timer? aTimer;
private UnhookWindowsHookExSafeHandle HookID;
private const int WM_KEYDOWN = 0x0100;
private const int VK_NUMLOCK = 0x90;
private const int VK_CAPSLOCK = 0x14;
private static bool numKeyState = false;
private static bool capsKeyState = false;
public FlyoutViewModel ViewModel
{
get;
}
public FlyoutPage()
{
ViewModel = App.GetService<FlyoutViewModel>();
InitializeComponent();
dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
numKeyState = (PInvoke.GetKeyState(VK_NUMLOCK) & 1) == 1;
capsKeyState = (PInvoke.GetKeyState(VK_CAPSLOCK) & 1) == 1;
HookID = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_KEYBOARD_LL, HookCallback, null, 0);
App.FlyoutWindow.Closed += FlyoutWindowClosed;
}
private void FlyoutWindowClosed(object sender, WindowEventArgs args) => HookID.Close();
private LRESULT HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0 && wParam == WM_KEYDOWN)
{
var vkCode = Marshal.ReadInt32(lParam);
if (vkCode == VK_NUMLOCK)
{
StatusTextBlock.Text = numKeyState ? "StatusWords_NumUnlock".GetLocalized() : "StatusWords_NumLock".GetLocalized();
StatusFontIcon.Glyph = numKeyState ? "\uE785" : "\uE72E";
numKeyState = !numKeyState;
}
else if (vkCode == VK_CAPSLOCK)
{
StatusTextBlock.Text = capsKeyState ? "StatusWords_CapsUnlock".GetLocalized() : "StatusWords_CapsLock".GetLocalized();
StatusFontIcon.Glyph = capsKeyState ? "\uE785" : "\uE72E";
capsKeyState = !capsKeyState;
}
if(FlyoutWindowContextFlyout.IsOpen == false) FlyoutWindowContextFlyout.ShowAt(this);
}
return PInvoke.CallNextHookEx(null, nCode, wParam, lParam);
}
I try to include StatusTextBlock.Text = numKeyState ? "StatusWords_NumUnlock".GetLocalized() : "StatusWords_NumLock".GetLocalized(); in try catch, but nothing changed, still display a crash about "System. ExecutionInexception".
I use these NuGet for core code:
- Microsoft.Windows.CsWin32 version: 0.3.49-beta
- Microsoft.WindowsAppSDK version: 1.5.240311000
This happens because you're running on .NET wich as a garbage collector (GC) which can move things around without telling you. Hey, that's called "managed" for a reason :-)
But when you hook the keyboard using a native API, you give Windows a raw pointer that's always supposed to point to your method code. If GC changes what the pointer points to, all sort of things can happen at callback time, such as Execution Engine Exceptions, or crashes, etc. This is what you see.
To fix this, you must ask GC to not move your method code. You can use a GCHandle for that or simply create a reference to a static method.
That's what demonstrated here, I keep FlyoutPage's
HookCallbackinstance method almost as is (result is not needed anymore) but give Windows a real static hook method that never moves during runtime: