
Drawing Issues in Windows11 24H2
Disclaimer:
I am Japanese, so my English may not be perfect. If there are any mistakes, I apologize in advance.
A Simple Explanation of GDI and HDC & Monitoring Code in Delphi
Today, I’d like to talk about a drawing issue that occurred in Windows 11 24H2 and the solution we implemented. To make it easy for even non-engineers to understand, I’ll start by explaining some basic terms.
What are GDI and HDC?
GDI (Graphics Device Interface)
GDI is an internal tool used by Windows to draw text, images, shapes, and other elements on the screen.
Simply put, GDI is like the “engine” for screen rendering.
Applications use this engine to “draw” the content they want to display.
HDC (Handle to Device Context)
HDC is like a “ticket” or “pass” that lets you interact with GDI.
With an HDC, you can instruct the GDI (the drawing engine) exactly where to “paint.”
In other words, you can think of an HDC as the access credential for a drawing surface.
What Happened in Windows 11 24H2?
Traditionally, when drawing with Delphi’s VCL (Visual Component Library), resource management was handled internally by the GDI. However, starting with Windows 11 24H2, a slight change in how GDI manages resources led to unexpected drawing issues.
The Issue:
During the drawing process, the internal management of GDI resources would break at some point, causing images not to display correctly on the screen.
The Solution:
Instead of relying on VCL’s automatic management, we bypassed it by directly using the HDC for drawing. This approach helped avoid the resource management problem.
How Did We Investigate the Issue?
We implemented low-level monitoring code using Delphi’s “inline assembler” to keep an eye on the GDI resource usage. In simple terms, we logged in real time how many drawing resources were being used to pinpoint where the issue was occurring.
Below is a part of the Delphi code we used. The code logs the number of GDI objects before and after drawing, and it uses inline assembler to capture and log the value of the HDC (specifically the SrcDC value) during the drawing process.
delphi
unit GdiMonitorUnit;
interface
uses
Windows, SysUtils, Graphics, Controls, Forms, VirtualImageList;
type
TFormPrintAddressImp = class(TForm)
public
procedure DrawVirtualImage(VirtualImageList: TVirtualImageList;
const Index: Integer; DestBitmap: TBitmap);
end;
implementation
var
// Log output buffer (temporary storage for strings)
LogBuffer: array[0..255] of AnsiChar;
//-----------------------------------------------------------
// LogGdiUsage
// Logs the number of GDI objects currently in use by the process
//-----------------------------------------------------------
procedure LogGdiUsage(const prefix: string);
var
gdiCount: DWORD;
logMsg: string;
begin
gdiCount := GetGuiResources(GetCurrentProcess, GR_GDIOBJECTS);
logMsg := Format('%s GDI Objects: %d', [prefix, gdiCount]);
StrPCopy(LogBuffer, logMsg);
asm
push offset LogBuffer
call OutputDebugStringA
end;
end;
//-----------------------------------------------------------
// LogSolvedUsingHDC
// Logs the message "Solved using HDC"
//-----------------------------------------------------------
procedure LogSolvedUsingHDC;
begin
StrPCopy(LogBuffer, 'Solved using HDC');
asm
push offset LogBuffer
call OutputDebugStringA
end;
end;
//-----------------------------------------------------------
// LogValueFromRegister
// Logs the passed value (in this case, the value of SrcDC) in hexadecimal format
//-----------------------------------------------------------
procedure LogValueFromRegister(Value: DWORD); stdcall;
var
Msg: string;
begin
// Convert the value to a hexadecimal string
Msg := Format('SrcDC Value: $%.8x', [Value]);
OutputDebugString(PChar(Msg));
end;
//-----------------------------------------------------------
// DrawVirtualImage
// Retrieves an image from TVirtualImageList and draws it directly using HDC
//-----------------------------------------------------------
procedure TFormPrintAddressImp.DrawVirtualImage(VirtualImageList: TVirtualImageList;
const Index: Integer; DestBitmap: TBitmap);
var
SrcBmp: TBitmap;
SrcDC, DestDC: HDC;
BlendFunc: BLENDFUNCTION;
DestRect: TRect;
begin
// Log the number of GDI objects before drawing
LogGdiUsage('Before drawing:');
// Create a bitmap for the source image and retrieve the image
SrcBmp := TBitmap.Create;
try
SrcBmp.PixelFormat := pf32bit;
SrcBmp.Width := VirtualImageList.Width;
SrcBmp.Height := VirtualImageList.Height;
VirtualImageList.GetBitmap(Index, SrcBmp);
// Set up the destination bitmap (32-bit recommended)
DestBitmap.PixelFormat := pf32bit;
if (DestBitmap.Width = 0) or (DestBitmap.Height = 0) then
begin
DestBitmap.Width := VirtualImageList.Width;
DestBitmap.Height := VirtualImageList.Height;
end;
DestRect := Rect(0, 0, DestBitmap.Width, DestBitmap.Height);
DestDC := DestBitmap.Canvas.Handle;
// Fill the background with white
FillRect(DestDC, DestRect, GetStockObject(WHITE_BRUSH));
// Bypass VCL management by directly manipulating the HDC
SrcDC := CreateCompatibleDC(DestDC);
try
SelectObject(SrcDC, SrcBmp.Handle);
// Setup for AlphaBlend
BlendFunc.BlendOp := AC_SRC_OVER;
BlendFunc.BlendFlags := 0;
BlendFunc.SourceConstantAlpha := 255;
BlendFunc.AlphaFormat := AC_SRC_ALPHA;
// --- Inline Assembler Low-Level Monitoring Code Start ---
asm
// Move the value of SrcDC into EAX and push it onto the stack,
// then call LogValueFromRegister
mov eax, SrcDC
push eax
call LogValueFromRegister
// (With stdcall, the called function cleans up the stack automatically)
end;
// --- Inline Assembler Monitoring Code End ---
// Draw the image directly using HDC
if not Windows.AlphaBlend(DestDC,
0, 0, DestBitmap.Width, DestBitmap.Height,
SrcDC,
0, 0, SrcBmp.Width, SrcBmp.Height,
BlendFunc) then
begin
RaiseLastOSError;
end;
finally
DeleteDC(SrcDC);
end;
finally
SrcBmp.Free;
end;
// After drawing, log "Solved using HDC"
LogSolvedUsingHDC;
LogGdiUsage('After drawing:');
end;
end.
Summary
Link to the Summary Article
GDI vs. HDC:
GDI: The “engine” that renders content on the screen.
HDC: The “access ticket” to control that engine.
The Problem:
From Windows 11 24H2 onward, relying on VCL’s automatic management sometimes led to issues in managing GDI resources.
The Solution:
We bypassed the VCL’s automatic management by directly operating on the HDC and using AlphaBlend. This simplified the drawing process and avoided the resource management problem.
Role of the Monitoring Code:
By using inline assembler to log the HDC value (SrcDC) during drawing, we could monitor the internal state in real time. This made it easier to pinpoint and debug the problem.
This approach allowed us to overcome the drawing issues in Windows 11 24H2. If you face similar problems, please consider trying this method!