dotnet/wpf · error · InvalidOperationException

SR.Image_MustBeLocked

Error message

SR.Image_MustBeLocked

What it means

D3DImage.SetBackBuffer requires the image to be in a locked state: a lock must have been acquired via Lock/TryLock and not yet released with Unlock. If _lockCount is 0, calling SetBackBuffer throws InvalidOperationException(Image_MustBeLocked). The lock ensures the CPU and the composition thread agree on when the back-buffer pointer can be swapped safely.

Solutions

  1. Acquire the lock first: call Lock() (or TryLock with a timeout and check the bool result) before SetBackBuffer, then Unlock when done.
  2. Keep every SetBackBuffer/AddDirtyRect call inside the same Lock/Unlock bracket.
  3. Ensure the Lock, SetBackBuffer, AddDirtyRect, Unlock sequence runs on one thread (the D3DImage's UI thread affinity).

Example fix

// before
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface); // throws: not locked

// after
d3dImage.Lock();
try
{
    d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
}
finally
{
    d3dImage.Unlock();
}
Defensive patterns

Strategy: validation

Validate before calling

if (!isLocked) { d3dImage.Lock(); isLocked = true; }
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surfacePtr);

Try / catch

try { d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, ptr); }
catch (InvalidOperationException ex) when (ex.Message.Contains("lock"))
{
    d3dImage.Lock();
    d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, ptr);
}

Prevention

When it happens

Trigger: Calling SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface) without a preceding Lock() or successful TryLock(); calling it after Unlock already released the last lock; calling it from a different thread than the one that took the lock.

Common situations: D3D/Direct3D9 interop render loops where the initial setup path assigns the back buffer before entering the lock/render/unlock cycle; races where another thread unlocked between checking and calling; forgetting the lock in a one-time back-buffer assignment.

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/a595f0ca893d7d0f. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/D3DImage.cs:129

        ///
        ///     For best performance by type:
        ///         IDirect3DSurface9
        ///             Vista WDDM: non-lockable, created on IDirect3DDevice9Ex with 
        ///                         D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES and
        ///                         D3DCAPS2_CANSHARERESOURCE support.
        ///             Vista XDDM: Doesn't matter. Software copying is fastest.
        ///             non-Vista:  Lockable with GetDC support for the pixel format and 
        ///                         D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES support.
        ///
        /// </summary>
        public void SetBackBuffer(D3DResourceType backBufferType, IntPtr backBuffer, bool enableSoftwareFallback)
        {

            WritePreamble();
            
            if (_lockCount == 0)
            {
                throw new InvalidOperationException(SR.Image_MustBeLocked);
            }

            // In case the user passed in something like "(D3DResourceType)-1"
            if (backBufferType != D3DResourceType.IDirect3DSurface9)
            {
                throw new ArgumentOutOfRangeException(nameof(backBufferType));
            }

            // Early-out if the current back buffer equals the new one. If the front buffer
            // is not available and software fallback is not enabled, _pUserSurfaceUnsafe 
            // will be null and this check will fail. We don't want a null backBuffer to 
            // early-out when the front buffer isn't available.
            if (backBuffer != IntPtr.Zero && backBuffer == _pUserSurfaceUnsafe)
            {
                return;
            }
            
            SafeMILHandle newBitmap = null;

View on GitHub (pinned to 81131a70a4)