我尝试挂接键盘。当按下数字锁定键和大写锁定键时显示一个窗口,并在窗口中显示当前锁定键的状态。当我尝试快速单击数字锁定键或大写锁定键时,它会在 App.g.i.cs中崩溃。Visual Studio 显示 Microsoft.WinUI.dll 中发生类型为“system.executionengeneException”的未处理异常。
这是在 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();
});
}
我认为这次崩溃似乎是因为 TextBlock 的 Text 的快速变化而发生的。
这是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>
这是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);
}
我尝试在 try catch 中包含
StatusTextBlock.Text = numKeyState ? "StatusWords_NumUnlock".GetLocalized() : "StatusWords_NumLock".GetLocalized();
,但没有任何改变,仍然显示有关“System.ExecutionInException”的崩溃。
我使用这些 NuGet 作为核心代码:
发生这种情况是因为您在 .NET 上运行,它作为垃圾收集器 (GC) 可以在不通知您的情况下移动内容。嘿,这被称为“托管”是有原因的:-)
但是当您使用本机 API 挂钩键盘时,您为 Windows 提供了一个原始指针,该指针始终应该指向您的方法代码。如果 GC 改变了指针指向的内容,回调时可能会发生各种各样的事情,例如执行引擎异常,或崩溃等。这就是您所看到的。
要解决此问题,您必须“固定”该方法,以便 GC 不会移动它。您可以使用 GCHandle 来实现此目的,或者简单地创建对静态方法的引用。
这就是这里演示的,我几乎保持 FlyoutPage 的
HookCallback
实例方法不变(不再需要结果),但给 Windows 一个真正的静态钩子方法,在运行时永远不会移动:
public sealed partial class FlyoutPage : Page
{
// this static assignement will ensure GC doesn't move the procedure around
private static readonly HOOKPROC _hook = GlobalHookCallback;
private static readonly UnhookWindowsHookExSafeHandle _hookId;
static FlyoutPage()
{
// hook here (must be done in UI thread)
_hookId = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_KEYBOARD_LL, _hook, null, 0);
// unhook on exit (more or less useless)
AppDomain.CurrentDomain.ProcessExit += (s, e) => { _hookId.Close(); };
}
// the hook function must be static
private static LRESULT GlobalHookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
// lucky us WH_KEYBOARD_LL calls back on initial hooking thread, ie: the UI thread
// so no need for Dispatcher mumbo jumbo
// get your navigation service and defer to the instance method if found
var navigation = App.GetService<INavigationService>();
if (navigation?.Frame != null)
{
if (navigation.Frame.Content is not FlyoutPage)
{
navigation.NavigateTo(typeof(FlyoutViewModel).FullName!);
}
if (navigation.Frame.Content is FlyoutPage page)
{
page.HookCallback(nCode, wParam, lParam);
}
}
return PInvoke.CallNextHookEx(null, nCode, wParam, lParam);
}
public FlyoutPage()
{
... remove hook from here
}
private void HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
... just defined as void
}
}