C# WinUI 中,未知原因导致“System.ExecutionInException”崩溃

问题描述 投票:0回答:1

我尝试挂接键盘。当按下数字锁定键和大写锁定键时显示一个窗口,并在窗口中显示当前锁定键的状态。当我尝试快速单击数字锁定键或大写锁定键时,它会在 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="&#xE72E;" />
                </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 作为核心代码:

  • Microsoft.Windows.CsWin32 版本:0.3.49-beta
  • Microsoft.WindowsAppSDK 版本:1.5.240311000
c# winapi winui-3
1个回答
0
投票

发生这种情况是因为您在 .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 
    }
 }
© www.soinside.com 2019 - 2024. All rights reserved.