Fachartikel

Delphi E2010 tagLOGBRUSH und tagLOGBRUSH32: Typkonflikt beheben

Die Struktur LOGBRUSH führt in der Windows-API zwei verschiedene Leben. Einmal ist sie der lebendige GDI-Objektdeskriptor, den CreateBrushIndirect akzeptiert. Das andere Mal ist sie ihr serialisiertes Gegenstück innerhalb eines Enhanced-Metafile-Datensatzes, wo die EMF-Spezifikation Feldbreiten auf 32 Bit für eine plattformneutrale Speicherung festlegt. Diese beiden Dinge waren immer strukturell verschieden; Delphi hat nur früher zugelassen, so zu tun, als ob sie gleich wären.

Bis Delphi XE (und grob gesagt allem vor der Umbenennung der Scope-Unit) waren die Windows-Header-Übersetzungen so großzügig, dass die Zuweisung von tagLOGBRUSH32 an TLogBrush ohne Beanstandung kompilierte. Delphi XE2 verschob die Windows-API-Deklarationen in Winapi.Windows und verschärfte die Record-Definitionen. Damit hörte der Compiler auf, eine direkte Übergabe von Data^.lb an CreateBrushIndirect zu akzeptieren, da Data^.lb als tagLOGBRUSH32 typisiert ist und die Funktion TLogBrush erwartet — und diese beiden Record-Typen sind nicht mehr zuweisungskompatibel.

Die Fehlermeldung lautet E2010 Incompatible types: 'tagLOGBRUSH' and 'tagLOGBRUSH32'. Der auslösende Code sieht so aus:

procedure VEMRCREATEBRUSHINDIRECT(Data: PEMRCreateBrushIndirect);
begin
  GDIObjects[Data^.ihBrush] := CreateBrushIndirect(Data^.lb);
end;

Die direkte Übergabe von Data^.lb funktionierte vor XE2. Das gilt nicht mehr, und ein Pointer-Cast ist die falsche Reaktion. Die binären Layouts sind ähnlich genug, dass ein Cast nicht sofort abstürzt, aber die Konvertierung wird still im Code vergraben und lässt Reviewer ohne jeden Hinweis, dass überhaupt eine Konvertierung stattfindet. Wenn ein zukünftiges SDK-Update einen Feldalias ändert oder Padding hinzufügt, übergibt ein Cast die falschen Bytes ohne jede Compiler-Warnung.

Die korrekte Lösung ist ein lokales TLogBrush mit explizitem Feldkopieren:

procedure VEMRCREATEBRUSHINDIRECT(Data: PEMRCreateBrushIndirect);
var
  LogBrush: TLogBrush;
begin
  LogBrush.lbStyle := Data^.lb.lbStyle;
  LogBrush.lbColor := Data^.lb.lbColor;
  LogBrush.lbHatch := Data^.lb.lbHatch;
  GDIObjects[Data^.ihBrush] := CreateBrushIndirect(LogBrush);
end;

Was das Feldkopieren eigentlich tut

tagLOGBRUSH32 ist die Form, die das EMF-Format für die Serialisierung verwendet. Sein Feld lbHatch ist 32 Bit breit, was die stabile Größe ist, die das Metafile-Format über Plattformen und Bitgrenzen hinweg garantiert. TLogBrush hingegen ist die Struktur, die CreateBrushIndirect direkt an den Kernel weitergibt. Bei einem 64-Bit-Windows-Build ist lbHatch in TLogBrush ein zeigerskaliertes ULONG_PTR, das auf 64 Bit erweitert wird. Das ist kein Detail, das der Compiler stillschweigend überbrücken kann.

Das individuelle Kopieren der drei Felder gibt dem Compiler das vollständige Bild der Absicht. lbStyle trägt den Pinseltyp (Vollton, Schraffur, Muster, Hohl usw.). lbColor trägt den COLORREF. lbHatch trägt je nach lbStyle entweder einen Schraffurindex oder ein Bitmap-Handle. Alle drei sind im Debugger direkt lesbar und für Code-Reviewer sichtbar, ohne Header-Dateien zu durchsuchen, um zu verstehen, was ein Pointer-Cast verdeckte.

Warum der Fehler bei Delphi-Upgrades auftritt

Veralteter EMF-Playback-Code wurde oft gegen Delphis altes Windows.pas geschrieben, wo Typ-Aliase weniger streng waren und Zuweisungen zwischen strukturell ähnlichen Records in mehr Fällen erlaubt waren. Beim Upgrade auf XE2 oder höher, wenn der Compiler die Winapi.Windows-Deklarationen zu sehen beginnt, zieht er eine härtere Grenze um die Record-Identität. Der E2010-Fehler ist kein überraschend neues Verhalten; die strengere Prüfung macht lediglich die bereits vorhandene API-Grenze sichtbar. Die EMF-Struktur und die GDI-Aufrufstruktur waren nie dasselbe, auch wenn Delphi früher erlaubte, sie so zu behandeln.

Der Migrationspfad ist daher mechanisch: Alle Stellen im EMF-Replay-Loop finden, wo ein tagLOGBRUSH32-Feld in einen GDI-Aufruf fließt, und ein lokales TLogBrush mit dem dreiteiligen Kopieren einfügen. Bei einem Parser, der viele Record-Typen mit Pinselparametern behandelt, hält ein kleiner Konvertierungs-Helfer das Muster zusammen:

function LogBrushFrom32(const B: tagLOGBRUSH32): TLogBrush;
begin
  Result.lbStyle := B.lbStyle;
  Result.lbColor := B.lbColor;
  Result.lbHatch := B.lbHatch;
end;

Überall dort, wo der alte Code Data^.lb direkt übergab, wird LogBrushFrom32(Data^.lb) aufgerufen. Der Replay-Loop bleibt lesbar und die API-Grenze bleibt explizit. Vom Compiler wird hier nichts weiter benötigt, und auf 64-Bit-Zielen gibt es nichts Überraschendes, sobald die Feldbreiten korrekt gehandhabt werden.