File: System\Windows\Forms\Application.ThreadContext.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Runtime.ExceptionServices;
using System.Windows.Forms.Primitives;
using Microsoft.Office;
 
namespace System.Windows.Forms;
 
public sealed partial class Application
{
    /// <summary>
    ///  This class is the embodiment of TLS for windows forms. We do not expose this to end users because
    ///  TLS is really just an unfortunate artifact of using Win 32. We want the world to be free
    ///  threaded.
    /// </summary>
    internal abstract unsafe partial class ThreadContext : MarshalByRefObject, IHandle<HANDLE>
    {
        private bool _oleInitialized;
        private bool _externalOleInit;
        private bool _inThreadException;
        private bool _filterSnapshotValid;
 
        private static readonly Dictionary<uint, ThreadContext> s_contextHash = [];
 
        private static readonly Lock s_lock = new();
        private readonly Lock _marshallingControlLock = new();
 
        private static int s_totalMessageLoopCount;
        private static msoloop s_baseLoopReason;
 
        [ThreadStatic]
        private static ThreadContext? t_currentThreadContext;
 
        internal ThreadExceptionEventHandler? _threadExceptionHandler;
        internal EventHandler? _idleHandler;
        internal EventHandler? _enterModalHandler;
        internal EventHandler? _leaveModalHandler;
 
        // Parking window list
        private readonly List<ParkingWindow> _parkingWindows = [];
        private Control? _marshallingControl;
        private List<IMessageFilter>? _messageFilters;
        private List<IMessageFilter>? _messageFilterSnapshot;
        private int _inProcessFilters;
        private HANDLE _handle;
        private readonly uint _id;
        protected int _messageLoopCount;
        private int _modalCount;
 
        // Used for correct restoration of focus after modality
        private WeakReference<Control>? _activatingControlRef;
 
        private ThreadWindows? _threadWindows;
        private int _disposed;
 
        // Debug helper variable
#if DEBUG
        private int _debugModalCounter;
#endif
 
        // A private field on Application that stores the callback delegate
        private MessageLoopCallback? _messageLoopCallback;
 
        protected Form? CurrentForm { get; private set; }
        protected bool PostedQuit { get; private set; }
 
        /// <summary>
        ///  Creates a new thread context object.
        /// </summary>
        protected ThreadContext()
        {
            HANDLE target;
 
            PInvoke.DuplicateHandle(
                PInvoke.GetCurrentProcess(),
                PInvoke.GetCurrentThread(),
                PInvoke.GetCurrentProcess(),
                &target,
                0,
                false,
                DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS);
 
            _handle = target;
 
            _id = PInvokeCore.GetCurrentThreadId();
            _messageLoopCount = 0;
            t_currentThreadContext = this;
 
            lock (s_lock)
            {
                s_contextHash[_id] = this;
            }
        }
 
        public ApplicationContext? ApplicationContext { get; private set; }
 
        public virtual void EnsureReadyForIdle() { }
 
        internal bool CustomThreadExceptionHandlerAttached => _threadExceptionHandler is not null;
 
        /// <summary>
        ///  Retrieves the actual parking form. This will demand create the parking window if it needs to.
        /// </summary>
        internal ParkingWindow GetParkingWindow(DPI_AWARENESS_CONTEXT context)
        {
            lock (_parkingWindows)
            {
                ParkingWindow? parkingWindow = GetParkingWindowForContext(context);
                if (parkingWindow is null)
                {
#if DEBUG
                    if (CoreSwitches.PerfTrack.Enabled)
                    {
                        Debug.WriteLine("Creating parking form!");
                        Debug.WriteLine(CoreSwitches.PerfTrack.Enabled, Environment.StackTrace);
                    }
#endif
 
                    using (ScaleHelper.EnterDpiAwarenessScope(context))
                    {
                        parkingWindow = new ParkingWindow();
                        s_parkingWindowCreated = true;
                    }
 
                    _parkingWindows.Add(parkingWindow);
                }
 
                return parkingWindow;
            }
        }
 
        /// <summary>
        ///  Returns existing parking window that matches the given dpi awareness context, if one exists.
        /// </summary>
        private ParkingWindow? GetParkingWindowForContext(DPI_AWARENESS_CONTEXT context)
        {
            if (_parkingWindows.Count == 0)
            {
                return null;
            }
 
            // Legacy OS/target framework scenario where ControlDpiContext is set to DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_UNSPECIFIED
            // because of 'ThreadContextDpiAwareness' API unavailability or this feature is not enabled.
            if (context.IsEquivalent(DPI_AWARENESS_CONTEXT.UNSPECIFIED_DPI_AWARENESS_CONTEXT))
            {
                Debug.Assert(_parkingWindows.Count == 1, "parkingWindows count can not be > 1 for legacy OS/target framework versions");
 
                return _parkingWindows[0];
            }
 
            // Supported OS scenario.
            foreach (ParkingWindow window in _parkingWindows)
            {
                if (context.IsEquivalent(window.DpiAwarenessContext))
                {
                    return window;
                }
            }
 
            // Parking window is not yet created for the requested DpiAwarenessContext
            return null;
        }
 
        internal Control? ActivatingControl
        {
            get => _activatingControlRef?.TryGetTarget(out Control? target) ?? false ? target : null;
            set => _activatingControlRef = value is null ? null : new(value);
        }
 
        /// <summary>
        ///  Retrieves the thread's marshalling control.
        /// </summary>
        internal Control MarshallingControl
        {
            get
            {
                if (_marshallingControl is { } control)
                {
                    return control;
                }
 
                lock (_marshallingControlLock)
                {
                    if (_marshallingControl is null)
                    {
#if DEBUG
                        if (CoreSwitches.PerfTrack.Enabled)
                        {
                            Debug.WriteLine("Creating marshalling control!");
                            Debug.WriteLine(CoreSwitches.PerfTrack.Enabled, Environment.StackTrace);
                        }
#endif
 
                        _marshallingControl = new ContextMarshallingControl();
                    }
 
                    return _marshallingControl;
                }
            }
        }
 
        /// <summary>
        ///  Allows you to setup a message filter for the application's message pump. This
        ///  installs the filter on the current thread.
        /// </summary>
        internal void AddMessageFilter(IMessageFilter? filter)
        {
            _messageFilters ??= [];
            _messageFilterSnapshot ??= [];
 
            if (filter is not null)
            {
                _filterSnapshotValid = false;
                if (_messageFilters.Count > 0 && filter is IMessageModifyAndFilter)
                {
                    // Insert the IMessageModifyAndFilter filters first
                    _messageFilters.Insert(0, filter);
                }
                else
                {
                    _messageFilters.Add(filter);
                }
            }
        }
 
        // Called immediately before we begin pumping messages for a modal message loop.
        internal unsafe void BeginModalMessageLoop(ApplicationContext? context)
        {
#if DEBUG
            _debugModalCounter++;
#endif
            BeginModalMessageLoop();
 
            // This will initialize the ThreadWindows with proper flags.
            DisableWindowsForModalLoop(onlyWinForms: false, context);
 
            _modalCount++;
 
            if (_enterModalHandler is not null && _modalCount == 1)
            {
                _enterModalHandler(Thread.CurrentThread, EventArgs.Empty);
            }
        }
 
        protected virtual void BeginModalMessageLoop() { }
 
        // Disables windows in preparation of going modal. If parameter is true, we disable all
        // windows, if false, only windows forms windows (i.e., windows controlled by this MsoComponent).
        // See also IMsoComponent.OnEnterState.
        internal void DisableWindowsForModalLoop(bool onlyWinForms, ApplicationContext? context)
        {
            ThreadWindows? old = _threadWindows;
            _threadWindows = new ThreadWindows(onlyWinForms);
            _threadWindows.Enable(false);
            _threadWindows._previousThreadWindows = old;
 
            if (context is ModalApplicationContext modalContext)
            {
                modalContext.DisableThreadWindows(true, onlyWinForms);
            }
        }
 
        protected virtual void Dispose(bool disposing) { }
 
        private void DisposeInternal(bool disposing)
        {
            // Want to ensure both paths are guarded against double disposal.
            if (Interlocked.Exchange(ref _disposed, 1) == 1)
            {
                return;
            }
 
            Dispose(disposing);
 
            try
            {
                // We can only clean up if we're being called on our own thread.
                if (PInvokeCore.GetCurrentThreadId() != _id)
                {
                    Debug.Assert(!disposing, "Shouldn't be getting dispose from another thread.");
                    return;
                }
 
                DisposeThreadWindows();
 
                try
                {
                    RaiseThreadExit();
                }
                finally
                {
                    if (_oleInitialized && !_externalOleInit)
                    {
                        _oleInitialized = false;
                        PInvoke.OleUninitialize();
                    }
                }
            }
            finally
            {
                // We can always clean up this handle though.
                if (!_handle.IsNull)
                {
                    PInvoke.CloseHandle(_handle);
                    _handle = HANDLE.Null;
                }
 
                try
                {
                    if (s_totalMessageLoopCount == 0)
                    {
                        RaiseExit();
                    }
                }
                finally
                {
                    lock (s_lock)
                    {
                        s_contextHash.Remove(_id);
                    }
 
                    if (t_currentThreadContext == this)
                    {
                        t_currentThreadContext = null;
                    }
                }
            }
        }
 
        /// <summary>
        ///  Disposes this thread context object. Note that this will marshal to the owning thread.
        /// </summary>
        public void Dispose(bool postQuit, bool disposing = true)
        {
            // Unravel our message loop. This will marshal us over to the right thread, making the dispose() method async.
            if (_messageLoopCount > 0 && postQuit)
            {
                PostQuit();
                return;
            }
 
            DisposeInternal(disposing);
            GC.SuppressFinalize(this);
        }
 
        /// <summary>
        ///  Disposes of this thread's parking form.
        /// </summary>
        private void DisposeParkingWindow()
        {
            if (_parkingWindows.Count != 0)
            {
                // We take two paths here. If we are on the same thread as
                // the parking window, we can destroy its handle. If not,
                // we just null it and let it GC. When it finalizes it
                // will disconnect its handle and post a WM_CLOSE.
                //
                // It is important that we just call DestroyHandle here
                // and do not call Dispose. Otherwise we would destroy
                // controls that are living on the parking window.
                uint hwndThread = PInvoke.GetWindowThreadProcessId(_parkingWindows[0], out _);
                uint currentThread = PInvokeCore.GetCurrentThreadId();
 
                for (int i = 0; i < _parkingWindows.Count; i++)
                {
                    if (hwndThread == currentThread)
                    {
                        _parkingWindows[i].Destroy();
                    }
                }
 
                _parkingWindows.Clear();
            }
        }
 
        /// <summary>
        ///  Gets rid of all windows in this thread context. Nulls out window objects that we hang on to.
        /// </summary>
        internal void DisposeThreadWindows()
        {
            // We dispose the main window first, so it can perform any cleanup that it may need to do.
            try
            {
                ApplicationContext?.Dispose();
                ApplicationContext = null;
 
                // Then, we rudely destroy all of the windows on the thread
                ThreadWindows tw = new(onlyWinForms: true);
                tw.Dispose();
 
                // And dispose the parking form, if it isn't already
                DisposeParkingWindow();
            }
            catch
            {
            }
        }
 
        // Enables windows in preparation of stopping modal. If parameter is true, we enable all windows,
        // if false, only windows forms windows (i.e., windows controlled by this MsoComponent).
        // See also IMsoComponent.OnEnterState.
        internal void EnableWindowsForModalLoop(bool onlyWinForms, ApplicationContext? context)
        {
            if (_threadWindows is not null)
            {
                _threadWindows.Enable(true);
                Debug.Assert(_threadWindows is not null, "OnEnterState recursed, but it's not supposed to be reentrant");
                _threadWindows = _threadWindows._previousThreadWindows;
            }
 
            if (context is ModalApplicationContext modalContext)
            {
                modalContext.DisableThreadWindows(false, onlyWinForms);
            }
        }
 
        // Called immediately after we end pumping messages for a modal message loop.
        internal unsafe void EndModalMessageLoop(ApplicationContext? context)
        {
#if DEBUG
            _debugModalCounter--;
            Debug.Assert(_debugModalCounter >= 0, "Mis-matched calls to Application.BeginModalMessageLoop() and Application.EndModalMessageLoop()");
#endif
            // This will re-enable the windows.
            EnableWindowsForModalLoop(onlyWinForms: false, context);
            EndModalMessageLoop();
 
            _modalCount--;
 
            if (_leaveModalHandler is not null && _modalCount == 0)
            {
                _leaveModalHandler(Thread.CurrentThread, EventArgs.Empty);
            }
        }
 
        protected virtual void EndModalMessageLoop() { }
 
        /// <summary>
        ///  Exits the program by disposing of all thread contexts and message loops.
        /// </summary>
        internal static void ExitApplication() => ExitCommon(disposing: true);
 
        private static void ExitCommon(bool disposing)
        {
            lock (s_lock)
            {
                if (s_contextHash is not null)
                {
                    ThreadContext[] contexts = new ThreadContext[s_contextHash.Values.Count];
                    s_contextHash.Values.CopyTo(contexts, 0);
                    for (int i = 0; i < contexts.Length; ++i)
                    {
                        if (contexts[i].ApplicationContext is ApplicationContext context)
                        {
                            context.ExitThread();
                        }
                        else
                        {
                            contexts[i].Dispose(disposing);
                        }
                    }
                }
            }
        }
 
        /// <summary>
        ///  Our finalization. This shouldn't be called as we should always be disposed.
        /// </summary>
        ~ThreadContext()
        {
            // Don't call OleUninitialize as the finalizer is called on the wrong thread.
            // We can always clean up this handle, though.
            if (!_handle.IsNull)
            {
                PInvoke.CloseHandle(_handle);
                _handle = HANDLE.Null;
            }
        }
 
        // When a Form receives a WM_ACTIVATE message, it calls this method so we can do the
        // appropriate MsoComponentManager activation magic
        internal virtual void FormActivated(bool activate)
        {
        }
 
        /// <summary>
        ///  Sets this component as the tracking component - trumping any active component for message filtering.
        /// </summary>
        internal virtual void TrackInput(bool track)
        {
        }
 
        /// <summary>
        ///  Retrieves a ThreadContext object for the current thread
        /// </summary>
        internal static ThreadContext FromCurrent() => t_currentThreadContext ?? Create();
 
        private static ThreadContext Create()
        {
            ThreadContext context = LocalAppContextSwitches.EnableMsoComponentManager
                ? new ComponentThreadContext()
                : new LightThreadContext();
 
            return context;
        }
 
        /// <summary>
        ///  Retrieves a ThreadContext object for the given thread ID
        /// </summary>
        internal static ThreadContext? FromId(uint id)
        {
            if (!s_contextHash.TryGetValue(id, out ThreadContext? context) && id == PInvokeCore.GetCurrentThreadId())
            {
                context = Create();
            }
 
            return context;
        }
 
        /// <summary>
        ///  Determines if it is OK to allow an application to quit and shutdown
        ///  the runtime. We only allow this if we own the base message pump.
        /// </summary>
        internal static bool GetAllowQuit()
            => s_totalMessageLoopCount > 0 && s_baseLoopReason == msoloop.Main;
 
        /// <summary>
        ///  Retrieves the handle to this thread.
        /// </summary>
        public HANDLE Handle => _handle;
 
        HANDLE IHandle<HANDLE>.Handle => Handle;
 
        /// <summary>
        ///  Retrieves the ID of this thread.
        /// </summary>
        internal uint GetId() => _id;
 
        /// <summary>
        ///  Determines if a message loop exists on this thread.
        /// </summary>
        internal bool GetMessageLoop() => GetMessageLoop(mustBeActive: false);
 
        /// <summary>
        ///  Determines if a message loop exists on this thread.
        /// </summary>
        internal unsafe bool GetMessageLoop(bool mustBeActive)
        {
            bool? loopExists = GetMessageLoopInternal(mustBeActive, _messageLoopCount);
            if (loopExists.HasValue)
            {
                return loopExists.Value;
            }
 
            // Finally, check if a message loop has been registered
            MessageLoopCallback? callback = _messageLoopCallback;
            if (callback is not null)
            {
                return callback();
            }
 
            // Otherwise, we do not have a loop running.
            return false;
        }
 
        protected virtual bool? GetMessageLoopInternal(bool mustBeActive, int loopCount) => null;
 
        internal unsafe ApartmentState OleRequired()
        {
            if (!_oleInitialized)
            {
                HRESULT hr = PInvoke.OleInitialize(pvReserved: (void*)null);
 
                _oleInitialized = true;
                if (hr == HRESULT.RPC_E_CHANGED_MODE)
                {
                    // This could happen if the thread was already initialized for MTA
                    // and then we call OleInitialize which tries to initialize it for STA
                    // This currently happens while profiling.
                    _externalOleInit = true;
                }
            }
 
            return _externalOleInit ? ApartmentState.MTA : ApartmentState.STA;
        }
 
        private void OnAppThreadExit(object? sender, EventArgs e) => Dispose(postQuit: true);
 
        /// <summary>
        ///  Called when an un-trapped exception occurs in a thread. This allows the programmer to trap these, and, if
        ///  left un-trapped, throws a standard error dialog.
        /// </summary>
        internal void OnThreadException(Exception ex)
        {
            if (_inThreadException)
            {
                return;
            }
 
            _inThreadException = true;
            try
            {
                if (_threadExceptionHandler is not null)
                {
                    _threadExceptionHandler(Thread.CurrentThread, new ThreadExceptionEventArgs(ex));
                }
                else
                {
                    if (LocalAppContextSwitches.DoNotCatchUnhandledExceptions)
                    {
                        ExceptionDispatchInfo.Capture(ex).Throw();
                    }
 
                    if (SystemInformation.UserInteractive)
                    {
                        ThreadExceptionDialog dialog = new(ex);
                        DialogResult result = DialogResult.OK;
 
                        try
                        {
                            result = dialog.ShowDialog();
                        }
                        finally
                        {
                            dialog.Dispose();
                        }
 
                        switch (result)
                        {
                            case DialogResult.Abort:
                                Exit();
                                Environment.Exit(0);
                                break;
                            case DialogResult.Yes:
                                if (ex is WarningException warning)
                                {
                                    Help.ShowHelp(null, warning.HelpUrl, warning.HelpTopic);
                                }
 
                                break;
                        }
                    }
                    else
                    {
                        // Ignore unhandled thread exceptions. The user can
                        // override if they really care.
                    }
                }
            }
            finally
            {
                _inThreadException = false;
            }
        }
 
        internal void PostQuit()
        {
            // Per KB 183116: https://web.archive.org/web/20070510025823/http://support.microsoft.com/kb/183116
            //
            // WM_QUIT may be consumed by another message pump under very specific circumstances.
            // When that occurs, we rely on the STATE_POSTEDQUIT to be caught in the next
            // idle, at which point we can tear down.
            //
            // We can't follow the KB article exactly, because we don't have an HWND to PostMessage to.
            PInvoke.PostThreadMessage(_id, PInvokeCore.WM_QUIT, default, default);
            PostedQuit = true;
        }
 
        /// <summary>
        ///  Allows the hosting environment to register a callback
        /// </summary>
        internal void RegisterMessageLoop(MessageLoopCallback? callback) => _messageLoopCallback = callback;
 
        /// <summary>
        ///  Removes a message filter previously installed with addMessageFilter.
        /// </summary>
        internal void RemoveMessageFilter(IMessageFilter f)
        {
            if (_messageFilters is not null)
            {
                _filterSnapshotValid = false;
                _messageFilters.Remove(f);
            }
        }
 
        /// <summary>
        ///  Starts a message loop for the given reason.
        /// </summary>
        internal void RunMessageLoop(msoloop reason, ApplicationContext? context)
        {
            // Ensure that we attempt to apply theming before doing anything that might create a window.
            using ThemingScope scope = new(UseVisualStyles);
            RunMessageLoopInner(reason, context);
        }
 
        private void RunMessageLoopInner(msoloop reason, ApplicationContext? context)
        {
            if (reason == msoloop.ModalForm && !SystemInformation.UserInteractive)
            {
                throw new InvalidOperationException(SR.CantShowModalOnNonInteractive);
            }
 
            // If we've entered because of a Main message loop being pushed
            // (different than a modal message loop or DoEVents loop)
            // then clear the QUIT flag to allow normal processing.
            // this flag gets set during loop teardown for another form.
            if (reason == msoloop.Main)
            {
                PostedQuit = false;
            }
 
            if (s_totalMessageLoopCount++ == 0)
            {
                s_baseLoopReason = reason;
            }
 
            _messageLoopCount++;
 
            if (reason == msoloop.Main)
            {
                // If someone has tried to push another main message loop on this thread, ignore it.
                if (_messageLoopCount != 1)
                {
                    throw new InvalidOperationException(SR.CantNestMessageLoops);
                }
 
                ApplicationContext = context;
                ApplicationContext!.ThreadExit += OnAppThreadExit;
 
                if (ApplicationContext.MainForm is not null)
                {
                    ApplicationContext.MainForm.Visible = true;
                }
            }
 
            Form? oldForm = CurrentForm;
            if (context is not null)
            {
                CurrentForm = context.MainForm;
            }
 
            bool fullModal = false;
            HWND hwndOwner = default;
 
            if (reason is msoloop.ModalForm or msoloop.ModalAlert)
            {
                fullModal = true;
 
                // We're about to disable all windows in the thread so our modal dialog can be the top dog. Because this can interact
                // with external MSO things, and also because the modal dialog could have already had its handle created,
                // Check to see if the handle exists and if the window is currently enabled. We remember this so we can set the
                // window back to enabled after disabling everyone else. This is just a precaution against someone doing the
                // wrong thing and disabling our dialog.
 
                bool modalEnabled = CurrentForm is not null && CurrentForm.Enabled;
 
                BeginModalMessageLoop(context);
 
                // If the owner window of the dialog is still enabled, disable it now.
                // This can happen if the owner window is from a different thread or
                // process.
                if (CurrentForm is not null)
                {
                    hwndOwner = (HWND)PInvokeCore.GetWindowLong(CurrentForm, WINDOW_LONG_PTR_INDEX.GWL_HWNDPARENT);
                    if (!hwndOwner.IsNull)
                    {
                        if (PInvoke.IsWindowEnabled(hwndOwner))
                        {
                            PInvoke.EnableWindow(hwndOwner, false);
                        }
                        else
                        {
                            // Reset hwndOwner so we are not tempted to fiddle with it
                            hwndOwner = default;
                        }
                    }
                }
 
                // The second half of the modalEnabled flag above. Here, if we were previously
                // enabled, make sure that's still the case.
                if (CurrentForm is not null && CurrentForm.IsHandleCreated && PInvoke.IsWindowEnabled(CurrentForm) != modalEnabled)
                {
                    PInvoke.EnableWindow(CurrentForm, modalEnabled);
                }
            }
 
            try
            {
                bool result;
 
                // Register marshaller for background tasks. At this point,
                // need to be able to successfully get the handle to the
                // parking window. Only do it when we're entering the first
                // message loop for this thread.
                if (_messageLoopCount == 1)
                {
                    WindowsFormsSynchronizationContext.InstallIfNeeded();
                }
 
                // Need to do this in a try/finally. Also good to do after we installed the synch context.
                if (fullModal && CurrentForm is not null)
                {
                    CurrentForm.Visible = true;
                }
 
                result = RunMessageLoop(reason, fullModal);
            }
            finally
            {
                if (fullModal)
                {
                    EndModalMessageLoop(context);
 
                    // Again, if the hwndOwner was valid and disabled above, re-enable it.
                    if (!hwndOwner.IsNull)
                    {
                        PInvoke.EnableWindow(hwndOwner, true);
                    }
                }
 
                CurrentForm = oldForm;
                s_totalMessageLoopCount--;
                _messageLoopCount--;
 
                if (_messageLoopCount == 0)
                {
                    // Last message loop shutting down, restore the sync context that was in place before we started
                    // the first message loop.
                    WindowsFormsSynchronizationContext.Uninstall(turnOffAutoInstall: false);
                }
 
                if (reason == msoloop.Main)
                {
                    Dispose(postQuit: true, disposing: true);
                }
                else if (_messageLoopCount == 0)
                {
                    EndOuterMessageLoop();
                }
            }
        }
 
        protected abstract bool RunMessageLoop(msoloop reason, bool fullModal);
 
        protected virtual void EndOuterMessageLoop() { }
 
        internal bool ProcessFilters(ref MSG msg, out bool modified)
        {
            bool filtered = false;
 
            modified = false;
 
            // Account for the case where someone removes a message filter as a result of PreFilterMessage.
            // The message filter will be removed from the _next_ message.
 
            // If message filter is added or removed inside the user-provided PreFilterMessage function,
            // and user code pumps messages, we might re-enter ProcessFilter on the same stack, we
            // should not update the snapshot until the next message.
            if (_messageFilters is not null && !_filterSnapshotValid && _inProcessFilters == 0)
            {
                if (_messageFilterSnapshot is not null)
                {
                    _messageFilterSnapshot.Clear();
                    if (_messageFilters.Count > 0)
                    {
                        _messageFilterSnapshot.AddRange(_messageFilters);
                    }
                }
 
                _filterSnapshotValid = true;
            }
 
            _inProcessFilters++;
            try
            {
                if (_messageFilterSnapshot is not null && _messageFilterSnapshot.Count != 0)
                {
                    IMessageFilter filter;
                    int count = _messageFilterSnapshot.Count;
 
                    Message message = Message.Create(msg.hwnd, msg.message, msg.wParam, msg.lParam);
 
                    for (int i = 0; i < count; i++)
                    {
                        filter = _messageFilterSnapshot[i];
                        bool filterMessage = filter.PreFilterMessage(ref message);
 
                        // Make sure that we update the msg struct with the new result after the call to
                        // PreFilterMessage.
                        if (filter is IMessageModifyAndFilter)
                        {
                            msg.hwnd = (HWND)message.HWnd;
                            msg.message = (uint)message.MsgInternal;
                            msg.wParam = message.WParamInternal;
                            msg.lParam = message.LParamInternal;
                            modified = true;
                        }
 
                        if (filterMessage)
                        {
                            filtered = true;
                            break;
                        }
                    }
                }
            }
            finally
            {
                _inProcessFilters--;
            }
 
            return filtered;
        }
 
        /// <summary>
        ///  Message filtering routine that is called before dispatching a message.
        ///  If this returns true, the message is already processed. If it returns
        ///  false, the message should be allowed to continue through the dispatch
        ///  mechanism.
        /// </summary>
        internal bool PreTranslateMessage(ref MSG msg)
        {
            if (ProcessFilters(ref msg, out _))
            {
                return true;
            }
 
            if (!msg.IsKeyMessage())
            {
                return false;
            }
 
            if (msg.message == PInvokeCore.WM_CHAR)
            {
                // 1 = extended keyboard, 46 = scan code
                int breakLParamMask = 0x1460000;
                if ((int)(uint)msg.wParam == 3 && ((int)msg.lParam & breakLParamMask) == breakLParamMask)
                {
                    // wParam is the key character, which for ctrl-brk is the same as ctrl-C.
                    // So we need to go to the lparam to distinguish the two cases.
                    // You might also be able to do this with WM_KEYDOWN (again with wParam=3)
 
                    if (Debugger.IsAttached)
                    {
                        Debugger.Break();
                    }
                }
            }
 
            Control? target = Control.FromChildHandle(msg.hwnd);
            bool retValue = false;
 
            Message m = Message.Create(msg.hwnd, msg.message, msg.wParam, msg.lParam);
 
            if (target is not null)
            {
                if (NativeWindow.WndProcShouldBeDebuggable)
                {
                    // We don't want to do a catch in the debuggable case.
                    if (Control.PreProcessControlMessageInternal(target, ref m) == PreProcessControlState.MessageProcessed)
                    {
                        retValue = true;
                    }
                }
                else
                {
                    try
                    {
                        if (Control.PreProcessControlMessageInternal(target, ref m) == PreProcessControlState.MessageProcessed)
                        {
                            retValue = true;
                        }
                    }
                    catch (Exception e)
                    {
                        OnThreadException(e);
                    }
                }
            }
            else
            {
                // See if this is a dialog message -- this is for handling any native dialogs that are launched from
                // WinForms code. This can happen with ActiveX controls that launch dialogs specifically
 
                // First, get the first top-level window in the hierarchy.
                HWND hwndRoot = PInvoke.GetAncestor(msg.hwnd, GET_ANCESTOR_FLAGS.GA_ROOT);
 
                // If we got a valid HWND, then call IsDialogMessage on it. If that returns true, it's been processed
                // so we should return true to prevent Translate/Dispatch from being called.
                if (!hwndRoot.IsNull && PInvoke.IsDialogMessage(hwndRoot, in msg))
                {
                    return true;
                }
            }
 
            msg.wParam = m.WParamInternal;
            msg.lParam = m.LParamInternal;
 
            if (retValue)
            {
                return true;
            }
 
            return false;
        }
    }
}