How to listen to foreground window change safely in WinUI?

水 知 485 Reputation points
2026-08-20T09:16:08.4866667+00:00

Hi community!

I'm trying to write a class to get forground window change messages. I think it could get infos such as ClassName correctly. But I found that the app will crash in some cases. And the crash seems only happens when ForegroundWindow.Current.StartMonitoring() was invoked. I clicked the NavigationViewItem in my app, and click an exteral window, then my app will crash with System.ExecutionEngineException with error message:

System.ExecutionEngineException
  HResult=0x80131506
  Source=<Cannot evaluate the exception source>
  StackTrace:
<Cannot evaluate the exception stack trace>

Here's the code of the class.

    public class ForegroundWindow
    {
        public nint Handle => _hWnd;

        public string ClassName
        {
            get
            {
                lock (_hwndLock)
                {
                    if (_hWnd == 0)
                        return string.Empty;
                    var sb = new StringBuilder(512);
                    GetClassNameW((IntPtr)_hWnd, sb, sb.Capacity);
                    return sb.ToString();
                }
            }
        }

        public string Title
        {
            get
            {
                lock (_hwndLock)
                {
                    if (_hWnd == 0)
                        return string.Empty;
                    var sb = new StringBuilder(512);
                    GetWindowTextW((IntPtr)_hWnd, sb, sb.Capacity);
                    return sb.ToString();
                }
            }
        }

        public uint ProcessId => GetProcessIdCore();

        public string ProcessName => Process.GetProcessById((int)ProcessId).ProcessName;

        public System.Drawing.Rectangle? VisibleRect
        {
            get
            {
                lock (_hwndLock)
                {
                    if (_hWnd == 0)
                        return System.Drawing.Rectangle.Empty;

                    RECT rect = default;
                    int hr = DwmGetWindowAttribute(
                        (IntPtr)_hWnd,
                        DWMWA_EXTENDED_FRAME_BOUNDS,
                        ref rect,
                        Marshal.SizeOf(typeof(RECT))
                    );

                    if (hr == 0)
                    {
                        return new System.Drawing.Rectangle(
                            rect.left, rect.top,
                            rect.right - rect.left,
                            rect.bottom - rect.top
                        );
                    }
                    else
                    {
                        return null;
                    }
                }
            }
        }

        public HashSet<Action> CallbackActions = [];

        private readonly object _hwndLock = new();
        private bool _ismonitoring = false;

        public ForegroundWindow()
        {
        }

        public static ForegroundWindow Current
        {
            get
            {
                return LazyInitializer.Instance;
            }
        }

        private static class LazyInitializer
        {
            static LazyInitializer()
            {
            }
            public static readonly ForegroundWindow Instance = new();
        }

        public void StartMonitoring()
        {
            if (_ismonitoring)
                return;
            _ismonitoring = true;
            var hook = SetWinEventHook(
                EVENT_SYSTEM_FOREGROUND,
                EVENT_SYSTEM_FOREGROUND,
                HMODULE.Null,
                WinEventProc,
                0,
                0,
                WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS
            );

            //Task.Run(() =>
            //{
            //    while (GetMessage(out Windows.Win32.UI.WindowsAndMessaging.MSG msg, default, 0, 0))
            //    {
            //        TranslateMessage(in msg);
            //        DispatchMessage(in msg);
            //    }
            //});
        }

        // Callback
        private void WinEventProc(
            HWINEVENTHOOK hWinEventHook,
            uint eventType,
            HWND hwnd,
            int idObject,
            int idChild,
            uint dwEventThread,
            uint dwmsEventTime)
        {
            var currentHwnd = GetForegroundWindow();
            lock (_hwndLock)
            {
                _hWnd = currentHwnd;
            }
            //foreach (var action in CallbackActions)
            //{
            //    action.Invoke();
            //}
        }


        #region Win32
        private nint _hWnd;

        private unsafe uint GetProcessIdCore()
        {
            lock (_hwndLock)
            {
                if (_hWnd == 0)
                    return 0;
                GetWindowThreadProcessId((IntPtr)_hWnd, out uint pid);
                return pid;
            }
        }

        //private unsafe string CallWin32ToGetPWSTR(int bufferLength, Func<PWSTR, int, int> getter)
        //{
        //    var buffer = ArrayPool<char>.Shared.Rent(bufferLength);
        //    try
        //    {
        //        fixed (char* ptr = buffer)
        //        {
        //            getter(ptr, bufferLength);
        //            return new string(ptr);
        //        }
        //    }
        //    finally
        //    {
        //        ArrayPool<char>.Shared.Return(buffer);
        //    }
        //}

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int GetClassNameW(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("dwmapi.dll", PreserveSig = true)]
        private static extern int DwmGetWindowAttribute(
            IntPtr hwnd,
            int dwAttribute,
            ref RECT pvAttribute,
            int cbAttribute
        );

        private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
        #endregion
    }

If more code is needed, please tell me and I'll try to send a link. Nothing is private but if I include the link in this question, Microsoft Q&A will delete my question automatically.

Please help.

Windows development | WinUI
0 comments No comments

Answer accepted by question author
Danny Nguyen (WICLOUD CORPORATION) 8,725 Reputation points Microsoft External Staff Moderator
2026-08-20T10:37:55.0333333+00:00

Hi @水 知

The System.ExecutionEngineException (0x80131506) you are experiencing is a classic symptom of an unmanaged callback attempting to invoke a Garbage Collected (GC'ed) delegate.

Issue 1: Delegate Garbage Collection In your StartMonitoring method, you pass WinEventProc directly to SetWinEventHook. This essentially creates a new delegate instance on the fly as an unmanaged function pointer. Because you don't store this delegate instance in a class-level variable, the .NET Garbage Collector doesn't see any active managed references to it and promptly collects it. When a foreground window changes shortly after, the operating system attempts to invoke the freed memory, bringing down the application with an ExecutionEngineException.

Fix: Store the delegate as a private field in your class to keep it alive for the lifetime of the hook.

Issue 2: Event Hooks Require a Message Loop WINEVENT_OUTOFCONTEXT hooks require a standard Windows message loop (GetMessage / DispatchMessage) to receive the events asynchronously. If you execute StartMonitoring() on the WinUI main UI thread, its built-in Dispatcher handles this for you. However, if you execute it from a background thread (as you commented out with Task.Run), it will fail unless you provide it with an active message loop.

Here is my implementation that you can try to follow instead to resolve the crash:

public class ForegroundWindow
{
    // ...

    private readonly object _hwndLock = new();
    private bool _ismonitoring = false;

    // 1. Keep a strong reference to the delegate to prevent GC.
    private WINEVENTPROC _winEventProcDelegate; 

    // 2. Keep the handle in case you want to UnhookWinEvent later.
    private HWINEVENTHOOK _hook;

    public ForegroundWindow()
    {
        // Initialize and save the delegate locally inside the class instance.
        _winEventProcDelegate = new WINEVENTPROC(WinEventProc);
    }

    // ...

    public void StartMonitoring()
    {
        if (_ismonitoring)
            return;
        _ismonitoring = true;

        // Pass the saved delegate instance, preventing it from being GC'ed.
        _hook = SetWinEventHook(
            EVENT_SYSTEM_FOREGROUND,
            EVENT_SYSTEM_FOREGROUND,
            HMODULE.Null,
            _winEventProcDelegate, // <-- Use the instance variable here
            0,
            0,
            WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS
        );

        // Note: As long as StartMonitoring() is called on the application's Main UI thread,
        // it will inherit WinUI's message loop automatically and WINEVENT_OUTOFCONTEXT works fine.
    }
}

If you found my response helpful or informative, I would greatly appreciate it if you could follow this guidance or provide feedback.

Thank you.

Was this answer helpful?

1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.