見出し画像

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!

いいなと思ったら応援しよう!