dotnet/wpf · error · InvalidOperationException

InvalidOperationException

Error message

InvalidOperationException

What it means

HwndSubclass manages the bond between a managed dispatcher hook and a Win32 window procedure. This InvalidOperationException signals an invalid state transition: the hook callback (SubclassWndProc) reached the attach path while the HwndSubclass is in the Detached bond state, meaning the window proc hook was already torn down.

Solutions

  1. Ensure Dispose/Detach is only called after the window handle is fully released and no messages are in flight
  2. Do not call Attach on an HwndSubclass that has been detached; create a new HwndSubclass instead
  3. Guard window lifetime so WndProc messages are not delivered after Detach (destroy the window before detaching)
  4. Synchronize access to HwndSubclass from the thread that owns the HWND

Example fix

// before
hwndSubclass.Detach();
SendMessage(hwnd, WM_CLOSE, ...); // messages still arriving on detached subclass
// after
SendMessage(hwnd, WM_CLOSE, ...); // let window tear down first
hwndSubclass.Detach();
Defensive patterns

Strategy: try-catch

Validate before calling

if (hwndSubclass.Detached) { hwndSubclass = new HwndSubclass(handler); hwndSubclass.Attach(hwnd); }

Try / catch

try { subclass.Attach(hwnd); } catch (InvalidOperationException) { subclass = new HwndSubclass(handler); subclass.Attach(hwnd); }

Prevention

When it happens

Trigger: SubclassWndProc runs for a window message after the HwndSubclass was detached (Bond.Detached) but the hook callback is still invoked; typically a re-entrancy or race where Detach happened during message processing.

Common situations: Window destroyed and re-created while a control is still bound; calling Attach/Dispose sequences out of order; multithreaded access to the same HwndSubclass causing a detach mid-message-loop.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/6617e6b453185916. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/HwndSubclass.cs:276

        internal IntPtr SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
        {
            IntPtr retval = IntPtr.Zero;
            bool handled = false;
            WindowMessage message = (WindowMessage)msg;

            // If we are unattached and we receive a message, then we must have
            // been used as the original window proc.  In this case, we insert
            // ourselves as if the original window proc had been DefWindowProc.
            // We pass in DefWndProcStub as a workaround for a bug in UxTheme on
            // Windows XP. For details see the comment on the DefWndProcWrapper method.
            if(_bond == Bond.Unattached)
            {
                HookWindowProc(hwnd, new NativeMethods.WndProc(SubclassWndProc),
                    Marshal.GetFunctionPointerForDelegate(DefWndProcStub));
            }
            else if(_bond == Bond.Detached)
            {
                throw new InvalidOperationException();
            }

            IntPtr oldWndProc = _oldWndProc;    // in case we get detached during this method

            if(message == DetachMessage)
            {
                // We received our special message to detach.  Make sure it is intended
                // for us by matching the bridge.
                if(wParam == IntPtr.Zero || wParam == (IntPtr)_gcHandle)
                {
                    int param = (int)lParam;    // 0 - normal, 1 - force, 2 - force and forward
                    bool force = (param > 0);

                    retval = CriticalDetach(force) ? new IntPtr(1) : IntPtr.Zero ;
                    handled = (param < 2);
                }
            }
            else

View on GitHub (pinned to 81131a70a4)