Проблема, с которой сталкиваются разработчики, использующие Delphi, заключается в некорректном сравнении указателя на функцию с nil. В коде, предоставленном Anders Melander для его набора Drag Drop, используется следующий фрагмент в модуле DragDrop.pas:
var
URLMONDLL: THandle = 0;
_CopyStgMedium: function(const cstgmedSrc: TStgMedium; var stgmedDest: TStgMedium): HResult; stdcall = nil;
...
function CopyStgMedium(const SrcMedium: TStgMedium; var DstMedium: TStgMedium): boolean;
begin
// ...
if (URLMONDLL = 0) then
begin
URLMONDLL := LoadLibrary('URLMON.DLL');
if (URLMONDLL <> 0) then
@_CopyStgMedium := GetProcAddress(URLMONDLL, 'CopyStgMedium');
end;
if (@_CopyStgMedium = nil) then // E2008 Incompatible types
raise Exception.Create(sNoCopyStgMedium);
// ...
end;
Компиляция проходит для следующих вариантов проверки:
if (@@_CopyStgMedium = nil) then // Логическая ошибка, это указатель на процедурную переменную
if (Pointer(@_CopyStgMedium) = nil) then // Правильно
if not(Assigned(@_CopyStgMedium)) then // Правильно
Однако, при попытке сравнения @_CopyStgMedium с nil возникает ошибка E2008, указывающая на несовместимость типов. Это происходит даже несмотря на то, что оператор @ установлен в режим типизированных указателей ({$T+}).
Подтвержденный ответ заключается в том, что использование оператора @ в данном контексте является излишним и вводит в заблуждение. Правильный способ написания кода:
function CopyStgMedium(const SrcMedium: TStgMedium; var DstMedium: TStgMedium): boolean;
begin
// ...
if (URLMONDLL = 0) then
begin
URLMONDLL := LoadLibrary('URLMON.DLL');
if (URLMONDLL <> 0) then
_CopyStgMedium := GetProcAddress(URLMONDLL, 'CopyStgMedium');
end;
if not Assigned(_CopyStgMedium) then
raise Exception.Create(sNoCopyStgMedium);
// ...
end;
Оператор @ используется для разрешения неоднозначности между вызовом функции и обращением к ней. Следовательно, сравнение _CopyStgMedium = nil некорректно, так как это попытка вызвать функцию. В то же время, на первый взгляд, @_CopyStgMedium = nil должно работать, но из-за типизации указателей, @_CopyStgMedium является типизированным указателем на функцию и не может быть сравнен с nil. Возможен приведение типа с помощью Pointer(@_CopyStgMedium), но предпочтительнее использовать Assigned.
Документация утверждает, что @ при обращении к процедурной переменной возвращает нетипизированный указатель, но при включенной типизации адресов ({$T+}) это не так. В более старых версиях, например, в XE3, @ возвращает нетипизированный указатель даже с включенной типизацией адресов. Возможно, документация не соответствует действительности, и это является ошибкой компилятора. Рекомендуется подать сообщение об ошибке.
Альтернативный ответ заключается в том, что использование @ с процедурными переменными приводит к созданию типизированного указателя на процедуру, который несовместим с nil. Для исправления ситуации можно явно привести тип к Pointer, но более правильным решением является использование функции Assigned.
В заключение, для избежания подобных проблем рекомендуется не использовать оператор @ с процедурными переменными и отдавать предпочтение функции Assigned для проверки на присвоение значения.
Разработчики Delphi сталкиваются с ошибкой E2008 при попытке сравнения указателя на функцию с nil из-за неправильного использования оператора @ и недопонимания типов в языке программирования.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.