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!
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
SpDrawIconShadowcode is identical, so the issue is still present in 2.5.12.Where
SpDrawIconShadow(added by the TB2K patch toTB2Common.pas). It is reached for disabled icons over a classic (non-virtual)TImageList:What happens
SpDrawIconShadowbuilds its two work bitmaps withTBitmap,pf32bit+SetSize:B1.PixelFormat := pf32bit; B1.SetSize(ImageWidth, ImageHeight); // + B2For a 32-bit bitmap,
TBitmap.SetSizegoes throughVcl.Graphics.CopyBitmap, which doesGetDC(0),CreateCompatibleDC(ScreenDC)andCreateDIBSection(ScreenDC, ...), eachGDICheck-wrapped. If the screen DC is momentarily unusable, the call returns 0 and VCLraises
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:GDI exhaustion;
GetLastErrorisERROR_INVALID_HANDLE (6)— the screen DC is transiently invalidwhile the graphics subsystem is still settling after resume.
Only disabled icons hit it, because
SpDrawIconShadowis the only place that allocates ascreen-DC-dependent bitmap during
WM_PAINT. Ordinary controls paint into theBeginPaintDC 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_PAINTchain reachingSpDrawIconShadow -> TBitmap.SetSize, where the VCLGDICheckraises the exception.TicToolbar/BufferedPaintare our thin TB2K toolbar subclass and buffered-paint helper;the failing path is entirely inside SpTBXLib + VCL:
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 isassigned, VCL
GetHandleTypereports the bitmap as a DDB, so a laterScanLinecallsDIBNeeded -> CopyBitmap -> GetDC(0)again (and re-copies the bits). In other words, bothSetSizeandHandle := <DIB>route aScanLine-ableTBitmapback throughGetDC(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!