ODAC от Devart
…я хочу вспомнить о библиотеке компонентов, которую я активно использовал много лет назад, и с которой совершенно недавно столкнулся вновь. Речь идёт о библиотеке компонентов,
…я хочу вспомнить о библиотеке компонентов, которую я активно использовал много лет назад, и с которой совершенно недавно столкнулся вновь. Речь идёт о библиотеке компонентов,
…я хочу вспомнить о библиотеке компонентов, которую я активно использовал много лет назад, и с которой совершенно недавно столкнулся вновь. Речь идёт о библиотеке компонентов,
26 лет тому назад, 14 февраля 1995 года, компания Borland выпустила новый продукт для быстрой разработки приложений — Borland Delphi. Я могу писать это предложение
Различные операционные системы все больше теснят MS Windows в технических заданиях. Часто все усугубляется пугающим в недалеком прошлом словом «кроссплатформенность». Многие задачи могут потребовать серьезной
К нам обратился человек, который пожаловался на то, что его приложение работало нормально, пока он не добавил в него EurekaLog. После включения в проекте EurekaLog стало появляться исключение Out of system resources. Исключение возбуждалось вспомогательной функцией OutOfResources
из модуля Vcl.Graphics
.
Стек вызова для исключения выглядел следующий образом:
Само исключение возбуждается такой функцией:
procedure OutOfResources;
begin
raise EOutOfResources.Create(SOutOfResources);
end;
Которая в свою очередь вызывается из:
procedure GDIError;
const
BufSize = 256;
var
ErrorCode: Integer;
Buf: array [Byte] of Char;
begin
ErrorCode := GetLastError;
if (ErrorCode <> 0) and (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, ErrorCode, LOCALE_USER_DEFAULT, Buf, BufSize, nil) <> 0) then
raise EOutOfResources.Create(Buf)
else
OutOfResources;
end;
function GDICheck(Value: THandle): THandle;
begin
if Value = 0 then
GDIError;
Result := Value;
end;
Заметьте, что такая реализация в VCL имеет проблему: вне зависимоси от ошибки возбуждается исключение класса
EOutOfResources
, даже если ошибка не равнаERROR_NOT_ENOUGH_MEMORY
,ERROR_NO_SYSTEM_RESOURCES
(или аналогичной). Логичнее было бы возбуждать что-то типаEInvalidGraphicOperation
в общем случае и возбуждатьEOutOfResources
только для ошибок подобного типа.
Из полного баг-отчёта EurekaLog было видно, что показатели памяти и описателей находятся в разумной норме, т.е. проблема не в нехватке памяти. Откуда следует, что (вероятнее всего) GetLastError
вернула 0. В самом деле, строка в TransparentStretchBlt
, которая проваливает проверку GDICheck
выглядит следующим образом:
MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, SrcW, SrcH));
Из документации видно, что функция CreateCompatibleBitmap
не устанавливает значение GetLastError
при неудаче.
Впрочем, у функции не так много причин завершиться неудачей: либо ей переданы неверные аргументы, либо ей нехватает памяти для создания bitmap. Заметьте, что нехватка памяти также возможна, если в SrcW
и SrcH
находится «мусор», который «слишком большой». Таким образом, хотя мы не знаем точную причину неудачи CreateCompatibleBitmap
, но мы можем предположить, что проблема — в аргументах.
Значения SrcDC
, SrcW
и SrcH
являются параметрами функции и приходят в неё из TBitmap.Draw
:
TransparentStretchBlt
(ACanvas.FHandle, Left, Top, Right - Left, Bottom - Top,
Canvas.FHandle { SrcDC }, 0, 0,
FDIB.dsbm.bmWidth { SrcW }, FDIB.dsbm.bmHeight { SrcH },
MaskDC, 0, 0);
Где Canvas
— это поле FCanvas
bitmap-а, создаваемое по запросу, а FDIB
— поле из FImage: TBitmapImage
. Таким образом, все параметры (SrcDC
, SrcW
и SrcH
) приходят в функцию TransparentStretchBlt
из полей объекта класса TBitmap
.
Следовательно, TBitmap
, который пытается рисовать TSomeDBGrid.DrawCell
, повреждён. Поскольку исключения не происходит без EurekaLog, но происходит с EurekaLog, то содержимое памяти TBitmap
меняется при включении EurekaLog. Наиболее вероятное объяснение такого поведения: ошибка типа «use after free». Без отладочных инструментов в программе код может обратиться к уже удалённому TBitmap
и «успешно» выполнить с ним операцию — поскольку память освобождённых объектов не удаляется физически, а лишь помечается как «свободная», без изменения её содежимого. При добавлении в программу EurekaLog её конфигурация по умолчанию включает проверки памяти, которые стирают память при её освобождении.
Проверить эту гипотезу можно изменив настройку «When memory is released» в положение «Do nothing» и отключив опцию «Catch memory problems». Если после этого исключение EOutOfResources
пропадёт, то в коде имеется ошибка вида «use after free». Наиболее вероятна ошибка в коде SomeComponent
, но есть небольшой ненулевой шанс, что клиент нашёл ошибку в VCL.
К сожалению, мы не получили ответа от клиента.
К нам обратился человек, который пожаловался на то, что его приложение работало нормально, пока он не добавил в него EurekaLog. После включения в проекте EurekaLog стало появляться исключение Out of system resources. Исключение возбуждалось вспомогательной функцией OutOfResources
из модуля Vcl.Graphics
.
Стек вызова для исключения выглядел следующий образом:
Само исключение возбуждается такой функцией:
procedure OutOfResources;
begin
raise EOutOfResources.Create(SOutOfResources);
end;
Которая в свою очередь вызывается из:
procedure GDIError;
const
BufSize = 256;
var
ErrorCode: Integer;
Buf: array [Byte] of Char;
begin
ErrorCode := GetLastError;
if (ErrorCode <> 0) and (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, ErrorCode, LOCALE_USER_DEFAULT, Buf, BufSize, nil) <> 0) then
raise EOutOfResources.Create(Buf)
else
OutOfResources;
end;
function GDICheck(Value: THandle): THandle;
begin
if Value = 0 then
GDIError;
Result := Value;
end;
Заметьте, что такая реализация в VCL имеет проблему: вне зависимоси от ошибки возбуждается исключение класса
EOutOfResources
, даже если ошибка не равнаERROR_NOT_ENOUGH_MEMORY
,ERROR_NO_SYSTEM_RESOURCES
(или аналогичной). Логичнее было бы возбуждать что-то типаEInvalidGraphicOperation
в общем случае и возбуждатьEOutOfResources
только для ошибок подобного типа.
Из полного баг-отчёта EurekaLog было видно, что показатели памяти и описателей находятся в разумной норме, т.е. проблема не в нехватке памяти. Откуда следует, что (вероятнее всего) GetLastError
вернула 0. В самом деле, строка в TransparentStretchBlt
, которая проваливает проверку GDICheck
выглядит следующим образом:
MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, SrcW, SrcH));
Из документации видно, что функция CreateCompatibleBitmap
не устанавливает значение GetLastError
при неудаче.
Впрочем, у функции не так много причин завершиться неудачей: либо ей переданы неверные аргументы, либо ей нехватает памяти для создания bitmap. Заметьте, что нехватка памяти также возможна, если в SrcW
и SrcH
находится «мусор», который «слишком большой». Таким образом, хотя мы не знаем точную причину неудачи CreateCompatibleBitmap
, но мы можем предположить, что проблема — в аргументах.
Значения SrcDC
, SrcW
и SrcH
являются параметрами функции и приходят в неё из TBitmap.Draw
:
TransparentStretchBlt
(ACanvas.FHandle, Left, Top, Right - Left, Bottom - Top,
Canvas.FHandle { SrcDC }, 0, 0,
FDIB.dsbm.bmWidth { SrcW }, FDIB.dsbm.bmHeight { SrcH },
MaskDC, 0, 0);
Где Canvas
— это поле FCanvas
bitmap-а, создаваемое по запросу, а FDIB
— поле из FImage: TBitmapImage
. Таким образом, все параметры (SrcDC
, SrcW
и SrcH
) приходят в функцию TransparentStretchBlt
из полей объекта класса TBitmap
.
Следовательно, TBitmap
, который пытается рисовать TSomeDBGrid.DrawCell
, повреждён. Поскольку исключения не происходит без EurekaLog, но происходит с EurekaLog, то содержимое памяти TBitmap
меняется при включении EurekaLog. Наиболее вероятное объяснение такого поведения: ошибка типа «use after free». Без отладочных инструментов в программе код может обратиться к уже удалённому TBitmap
и «успешно» выполнить с ним операцию — поскольку память освобождённых объектов не удаляется физически, а лишь помечается как «свободная», без изменения её содежимого. При добавлении в программу EurekaLog её конфигурация по умолчанию включает проверки памяти, которые стирают память при её освобождении.
Проверить эту гипотезу можно изменив настройку «When memory is released» в положение «Do nothing». Если после этого исключение EOutOfResources
пропадёт, то в коде имеется ошибка вида «use after free». Наиболее вероятна ошибка в коде SomeComponent
, но есть небольшой ненулевой шанс, что клиент нашёл ошибку в VCL.
К сожалению, мы не получили ответа от клиента.