Skip to content

SpDrawIconShadow — EOutOfResources: "The handle is invalid" after the system resumes from sleep (confirmed in 2.5.12) #129

Description

@rubydenial

Hi,

Thanks for SpTBXLib. We use it in a Delphi 10.3.3 (Rio) application on Windows 11 x64 and traced an intermittent crash to SpDrawIconShadow.

We ship SpTBXLib 2.5.11. We also compared it against the latest release, 2.5.12: the SpDrawIconShadow code is identical, so the issue is still present in 2.5.12.

Where

SpDrawIconShadow (added by the TB2K patch to TB2Common.pas). It is reached for disabled icons over a classic (non-virtual) TImageList:

TSpTBXItemViewer.DrawItemImage -> SpDrawXPMenuItemImage
  -> SpDrawVirtualImageList(..., Enabled = False) -> SpDrawIconShadow

What happens

SpDrawIconShadow builds its two work bitmaps with TBitmap, pf32bit + SetSize:

B1.PixelFormat := pf32bit;  B1.SetSize(ImageWidth, ImageHeight);   // + B2

For a 32-bit bitmap, TBitmap.SetSize goes through Vcl.Graphics.CopyBitmap, which does
GetDC(0), CreateCompatibleDC(ScreenDC) and CreateDIBSection(ScreenDC, ...), each
GDICheck-wrapped. If the screen DC is momentarily unusable, the call returns 0 and VCL
raises EOutOfResources(SysErrorMessage(GetLastError)).

When

On the first toolbar repaint right after the machine resumes from a long sleep/hibernate
(a synchronous TSpTBXDock.Resize -> Update -> WM_PAINT). At that moment:

  • the per-process GDI handle count is low (~465, far from the 10,000 limit) — so it is not
    GDI exhaustion;
  • GetLastError is ERROR_INVALID_HANDLE (6) — the screen DC is transiently invalid
    while the graphics subsystem is still settling after resume.
SpDrawIconShadow -> TBitmap.SetSize -> CopyImage -> CopyBitmap -> GDICheck
  -> EOutOfResources("The handle is invalid")

Only disabled icons hit it, because SpDrawIconShadow is the only place that allocates a
screen-DC-dependent bitmap during WM_PAINT. Ordinary controls paint into the
BeginPaint DC and are unaffected.

Example: a real crash report

One of our captured crash logs (top frames; the repetitive lower message-loop frames are
omitted). It shows the synchronous TSpTBXDock.Resize -> Update -> WM_PAINT chain reaching
SpDrawIconShadow -> TBitmap.SetSize, where the VCL GDICheck raises the exception.
TicToolbar / BufferedPaint are our thin TB2K toolbar subclass and buffered-paint helper;
the failing path is entirely inside SpTBXLib + VCL:

EOutOfResources: The handle is invalid.

  System._RaiseExcept
  Vcl.Graphics.TCanvas.BrushChanged
  Vcl.Graphics.CreateGrayMappedRes           + $2D1
  Vcl.Graphics.TBitmap.CopyImage             + $BC
  Vcl.Graphics.TBitmap.SetSize               + $67
  Tb2common.SpDrawIconShadow                 + $CB     <-- pf32bit + SetSize buffers
  Tb2common.SpDrawVirtualImageList           + $6D8
  Sptbxitem.SpDrawXPMenuItemImage            + $131
  Sptbxitem.TSpTBXItemViewer.DrawItemImage   + $184
  Sptbxitem.TSpTBXItemViewer.Paint           + $FAF
  Tb2item.TTBView.DrawItem                   + $518
  Tb2item.TTBView.DrawSubitems               + $87
  Tb2toolbar.TTBCustomToolbar.Paint          + $9D
  Icmenucontrols.TicToolbar.Paint            + $179    (our TB2K toolbar subclass)
  Vcl.Controls.TCustomControl.PaintWindow
  Vcl.Controls.TWinControl.PaintHandler
  Icvclutils.BufferedPaint                   + $144    (our buffered-paint helper)
  Icmenucontrols.TicToolbar.WMPaint          + $1D
  ... (TObject.Dispatch / WndProc -> WM_PAINT) ...
  Vcl.Controls.TWinControl.Update            + $20
  Sptbxitem.TSpTBXDock.Resize                + $27A    <-- resize after the system resumed
  Vcl.Controls.TWinControl.AlignControls
  Vcl.Forms.TCustomForm.WndProc
  ... (message loop) ...
  Vcl.Forms.TApplication.Run
  IDE.IDE (Line 324)

What we tried

We did not find a clean fix on our side, so we are leaving the proper fix to you. For the
record, the obvious idea — allocating the buffers without the screen DC via
B1.Handle := CreateDIBSection(0, ...) — does not help: once an external handle is
assigned, VCL GetHandleType reports the bitmap as a DDB, so a later ScanLine calls
DIBNeeded -> CopyBitmap -> GetDC(0) again (and re-copies the bits). In other words, both
SetSize and Handle := <DIB> route a ScanLine-able TBitmap back through GetDC(0).

Environment

SpTBXLib 2.5.11 (in production) — also verified present in the current 2.5.12; Delphi 10.3.3 Rio (RTL/VCL 26.0); Windows 11 x64.

Thank you for maintaining SpTBXLib!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions