You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设置NavigationCacheMode后导航返回触发CallWindowProc栈溢出异常

解决NavigationCacheMode启用后CallWindowProc引发的StackOverflowException问题

问题根源

当你把NavigationCacheMode设为Enabled或Required后,页面实例会被缓存起来,导航离开再返回时不会重新创建。但你的MainPage_Loaded事件每次页面加载(包括从缓存返回)都会执行,每次执行都会调用SetWindowLongPtr替换窗口过程——这就导致窗口过程被一层一层Hook:新的Hook指向你的HotKeyProc,而上一次的Hook过程被存在origHotKeyProc里。等窗口消息触发时,HotKeyProc调用CallWindowProc到上一个Hook,那个Hook又会调用它自己的CallWindowProc,无限套娃直接把栈撑爆,就出现StackOverflowException了。

另外还有两个潜在坑:

  1. 没在页面卸载时清理Hook和热键注册,就算不缓存,多次导航也会重复Hook;
  2. 委托hotKeyProcD可能被GC回收,导致调用无效指针,触发类似你提到的ExecutionEngineException。

解决方案

核心思路

  • 窗口是全局的(整个应用只有一个主窗口),Hook逻辑只需要执行一次,不能每次页面加载都重复操作;
  • 必须在合适的时机清理注册的热键和Hook的窗口过程,避免内存泄漏和无效调用。

修改后的代码示例

MainPage.xaml.cs 修改

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;

namespace NavigationProc;

public sealed partial class MainPage : Page
{
    // 用静态变量标记是否已经Hook过,避免重复操作
    private static bool _isWndProcHooked = false;
    private static WNDPROC? _origHotKeyProc;
    private static WNDPROC? _hotKeyProcD;
    private static HWND _mainWindowHandle;

    public MainPage()
    {
        InitializeComponent();
        Loaded += MainPage_Loaded;
    }

    private static LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam)
    {
        const uint WM_HOTKEY = 0x0312;

        if (Msg == WM_HOTKEY)
        {
            // 从全局窗口里找到当前的MainPage实例
            if (App.MainWindow.Content is Frame frame && frame.Content is MainPage mainPage)
            {
                if (mainPage.ToggleButton.IsEnabled)
                {
                    mainPage.ToggleButton.IsChecked = !mainPage.ToggleButton.IsChecked;
                }
            }
        }

        return PInvoke.CallWindowProc(_origHotKeyProc, hWnd, Msg, wParam, lParam);
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (_isWndProcHooked) return;

        // 获取并保存主窗口句柄
        _mainWindowHandle = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow));

        // 注册F6热键
        const int hotKeyId = 0x0000;
        _ = PInvoke.RegisterHotKey(_mainWindowHandle, hotKeyId, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75);

        // 只Hook一次窗口过程
        _hotKeyProcD = HotKeyProc;
        IntPtr hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(_hotKeyProcD);
        IntPtr wndPtr = PInvoke.SetWindowLongPtr(_mainWindowHandle, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyProcPtr);
        _origHotKeyProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(wndPtr);

        _isWndProcHooked = true;
    }

    // 提供静态方法,用于窗口关闭时清理资源
    public static void CleanupHotKeyAndHook()
    {
        if (!_isWndProcHooked) return;

        // 注销热键
        const int hotKeyId = 0x0000;
        _ = PInvoke.UnregisterHotKey(_mainWindowHandle, hotKeyId);

        // 恢复原始窗口过程
        if (_origHotKeyProc != null)
        {
            IntPtr origProcPtr = Marshal.GetFunctionPointerForDelegate(_origHotKeyProc);
            _ = PInvoke.SetWindowLongPtr(_mainWindowHandle, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, origProcPtr);
        }

        _isWndProcHooked = false;
        _hotKeyProcD = null;
        _origHotKeyProc = null;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _ = Frame.Navigate(typeof(OtherPage));
    }
}

在App.xaml.cs中添加窗口关闭时的清理逻辑

using Microsoft.UI.Xaml;

namespace NavigationProc;

public partial class App : Application
{
    public static MainWindow MainWindow { get; private set; }

    public App()
    {
        InitializeComponent();
    }

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        MainWindow = new MainWindow();
        MainWindow.Closed += MainWindow_Closed;
        MainWindow.Activate();
    }

    private void MainWindow_Closed(object sender, WindowEventArgs args)
    {
        // 窗口关闭时清理热键和Hook
        MainPage.CleanupHotKeyAndHook();
    }
}

关键说明

  • 用静态变量标记Hook状态,确保窗口过程只被替换一次,彻底避免多层Hook导致的递归;
  • 热键处理中通过全局窗口实例查找页面控件,适配页面缓存的场景;
  • 静态委托_hotKeyProcD不会被GC回收,解决了潜在的ExecutionEngineException问题;
  • 窗口关闭时清理资源,避免内存泄漏和无效指针调用。

内容的提问来源于stack exchange,提问作者Ryan Luu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:13:17