Техническая статья

Исправление Delphi E2010 для несовместимых tagLOGBRUSH и tagLOGBRUSH32

Delphi XE2 и более новые версии перенесли большую часть объявлений Windows API в область видимости модуля Winapi.Windows и ужесточили несколько объявлений записей. Поэтому код, который собирался в старых версиях Delphi, может перестать компилироваться, когда запись Enhanced Metafile предоставляет значение tagLOGBRUSH32 а GDI API ожидает tagLOGBRUSH или TLogBrush.

Обычно это проявляется при воспроизведении записей EMF или восстановлении объектов кисти GDI из PEMRCreateBrushIndirect. Прямой вызов вроде следующего выглядит разумно, но в новых компиляторах Delphi эти два типа записей не совместимы для присваивания.

1
2
3
4
5
procedure VEMRCREATEBRUSHINDIRECT(Data: PEMRCreateBrushIndirect);
begin
  GDIObjects[Data^.ihBrush] := CreateBrushIndirect(Data^.lb);
//GDIObjects and CreateBrushIndirect are defined in the winapi.windows unit.
end;

Компилятор сообщает E2010 Incompatible types: 'tagLOGBRUSH' and 'tagLOGBRUSH32'Не проталкивайте запись через небезопасное приведение. Более надежное исправление — создать локальную TLogBrushявно скопировать совместимые поля и передать эту структуру в CreateBrushIndirect.

1
2
3
4
5
6
7
8
9
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;

Почему явное копирование безопаснее

tagLOGBRUSH32 используется в формате записей EMF, чтобы сериализованные данные метафайла имели стабильную 32-битную раскладку полей. CreateBrushIndirectпри этом принимает обычную структуру кисти Windows, используемую текущим объявлением API. Копирование lbStyleи lbColorа также lbHatch документирует преобразование и убирает зависимость от совместимости записей в конкретном компиляторе.

Такой прием также проще проверять при переносе старого графического кода на Unicode Delphi или 64-битные цели. Если в будущих объявлениях Windows SDK псевдонимы типов снова изменятся, поэлементное преобразование ясно покажет предполагаемую границу данных.

Контрольный список переноса

  • Оставьте Winapi.Windows в списке uses для современных проектов Delphi.
  • Копируйте поля записи EMF в структуру API, которую ожидает вызов GDI.
  • Не заменяйте это слепым приведением указателя, пока не проверены двоичная раскладка и время жизни данных.
  • После создания кисти сохраните существующие правила владения объектами для таблицы GDIObjects .

Почему это появляется при обновлении Delphi

Старый код воспроизведения EMF часто обращается с записями Windows так, будто структуры API и сериализованные структуры метафайла взаимозаменяемы. Старые модули и более свободные псевдонимы иногда позволяли такой предпосылке компилироваться. Современные объявления Delphi строже, поэтому компилятор показывает границу, которая уже существовала в Windows API.

Следовательно, эта ошибка не только синтаксическая. Это полезное предупреждение о том, что код переходит от структуры файлового формата к живому вызову GDI. Явное преобразование делает код безопаснее при последующей проверке для 64-битных сборок, миграции Unicode или обновленных объявлений Windows SDK.

Чего не делать

Приведение указателя может показаться способом убрать ошибку компилятора, но оно скрывает реальное преобразование и усложняет дальнейшее сопровождение. Оно также не показывает проверяющим, какие поля используются намеренно. Копирование полей короткое, детерминированное и самодокументируемое, поэтому это лучший вариант по умолчанию для кода приложений и примеров компонентов.

Если ваш парсер EMF обрабатывает много похожих записей, вынесите это преобразование в небольшую вспомогательную функцию и протестируйте ее с несколькими стилями кистей. Так цикл воспроизведения останется читаемым, а граница Windows API будет явной.