Исключение Delphi Tokyo исключает результат настройки функции

Обнаружено, что обработка исключений в Delphi Tokyo ведет себя немного иначе, чем в предыдущих версиях Delphi.

function FuncTest: integer;
begin
  Result := 1;
  try
    raise Exception.Create('Error Message');
  finally
    Result := 2;
  end;
end;

function Test:integer;
begin
  Result:=0;
  try
    Result:=FuncTest;
  finally
    ShowMessage(Result.ToString);
  end;
end;

В более ранних версиях Delphi окно сообщений показывает здесь "2", Токио - "0". Это ошибка в Токио или исключения не должны обрабатываться так?

Ответ 1

Токийское поведение правильное. Функция, которая вызывает исключение, не возвращает значение. Вы до сих пор полагались на детали реализации.

Рассмотрим этот код:

Result:=FuncTest;

Это выполняется следующим образом:

  • FuncTest.
  • Result.

Теперь, поскольку шаг 1 вызывает исключение, шаг 2 не выполняется.

Если что-нибудь, я бы сказал, что поведение, о котором вы сообщаете из более ранних версий, сомнительно. В этой функции:

function Test:integer;
begin
  Result:=0;
  try
    Result:=FuncTest;
  finally
    ShowMessage(Result.ToString);
  end;
end;

Оператор Result:=FuncTest вызывает исключение, поэтому Result не должен быть изменен этим утверждением. Другой способ подумать о том, что функция вызывается, но назначение не выполняется.


Одной из проблем с Delphi ABI является то, что возвращаемые значения функции иногда реализуются как неявные параметры var. Это означает, что назначение может или не может произойти. Чтобы продемонстрировать:

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  TRec1 = record
    X1: NativeInt;
  end;

  TRec2 = record
    X1: NativeInt;
    X2: NativeInt;
  end;

function GetRec1: TRec1;
begin
  Result.X1 := 1;
  raise Exception.Create('');
end;

function GetRec2: TRec2;
begin
  Result.X1 := 1;
  raise Exception.Create('');
end;

procedure Main;
var
  Rec1: TRec1;
  Rec2: TRec2;
begin
  Rec1 := Default(TRec1);
  Writeln(Rec1.X1);
  try
    Rec1 := GetRec1;
  except
  end;
  Writeln(Rec1.X1);

  Rec2 := Default(TRec2);
  Writeln(Rec2.X1);
  try
    Rec2 := GetRec2;
  except
  end;
  Writeln(Rec2.X1);
end;

begin
  Main;
  Readln;
end.

Выводится:

0
0
0
1

Это довольно разочаровывает. Невозможно изменить переменную вызывающего, но использование неявного параметра var, а не возврат значения позволяет эту утечку. На мой взгляд, это серьезный недостаток в дизайне Delphi ABI, недостаток, которого вы не найдете на большинстве других языков.


В вашем коде нет параметра var, потому что возвращаемый тип переносится в регистр. В этом случае любая версия Delphi, которая выводит 2, сломается.

В принципе, ваш код ошибочен в своих ожиданиях. Если функция вызывает исключение, вы должны предположить, что возвращаемое значение не определено.

Наконец, ваш код выводит 0 в XE3 и XE7, поэтому мне интересно, как далеко назад вам нужно перейти к значению 2.

Ответ 2

Учти это:

    function FuncTest: integer;
    begin
      Result := 1;
      try
        try
          raise Exception.Create('Error Message');
        except
          { do nothing }
        end
      finally
        Result := 2;
      end;
    end;

Не думайте, что "finally" обрабатывает исключение локально, а не "возвращает исключение". Для локальной обработки требуется условие LOCAL try-exc.