Подтвердить что ты не робот

Почему изменения в объекте Exception теряются при повторном поднятии?

Я был уверен, что это работало для меня, и я видел его в сети (Джолион Смит и Дэвид Мурхаус). Просто попробовав его в простой программе как в D2007, так и в пробной версии XE2, он не сохраняет измененное сообщение. Как только произойдет "повышение", сообщение вернется к исходному исключению.

Какую очевидную вещь я теряю? Альтернативой является "поднять Exception.Create(...)", но я хочу просто пропустить исходное исключение в цепочке, только с дополнительной информацией, помеченной вдоль каждого блока исключений.

var a: Integer;
begin
  try
    a := 0;
    Label1.Caption := IntToStr(100 div a);
  except
    on e: Exception do
    begin
      e.Message := 'Extra Info Plus the original : ' + e.Message;
      raise;
    end;
  end;
end;
4b9b3361

Ответ 1

Ну удар меня! Это выглядело так неправильно, что я должен был попробовать это сам, и ты абсолютно прав! Я сузил его до того, что это исключение ОС (деление на ноль), которое генерируется самой ОС, а не Delphi. Если вы попытаетесь поднять EIntError самостоятельно, вы получите ожидаемое поведение, а не то, что вы видите выше. Обратите внимание, что ожидаемое поведение возникает всякий раз, когда вы сами создаете исключение.

Обновление: В модуле System.pas появляется следующий код, который возникает при повторном создании исключения:

{ Destroy any objects created for non-delphi exceptions }

MOV     EAX,[EDX].TRaiseFrame.ExceptionRecord
AND     [EAX].TExceptionRecord.ExceptionFlags,NOT cUnwinding
CMP     [EAX].TExceptionRecord.ExceptionCode,cDelphiException
JE      @@delphiException
MOV     EAX,[EDX].TRaiseFrame.ExceptObject
CALL    TObject.Free
CALL    NotifyReRaise

Итак, если исключение не является исключением Delphi (в данном случае исключением ОС), то исключение (измененное) "Delphi" освобождается и исходное исключение повторно создается, тем самым отбрасывая любые изменения, внесенные в исключение. Дело закрыто!

Обновить 2: (не мог с собой поделать). Вы можете воспроизвести это со следующим кодом:

type
  TThreadNameInfo = record
    InfoType: LongWord;  // must be $00001000
    NamePtr: PAnsiChar;  // pointer to message (in user address space)
    ThreadId: LongWord;  // thread id ($ffffffff indicates caller thread)
    Flags: LongWord;     // reserved for future use, must be zero
  end;

var
  lThreadNameInfo: TThreadNameInfo;

  with lThreadNameInfo do begin
    InfoType := $00001000;
    NamePtr := PAnsiChar(AnsiString('Division by zero'));
    ThreadId := $ffffffff;
    Flags := $00000000;
  end;
  RaiseException($C0000094, 0, sizeof(lThreadNameInfo) div sizeof(LongWord), @lThreadNameInfo);

Удачи!

Ответ 2

См. объяснение Миши. В качестве обходного пути вы можете сделать это:

except
  on E: Exception do
  begin
    E := Exception(ExceptObject);
    E.Message := '(Extra info) ' + E.Message;
    AcquireExceptionObject;
    raise E;
  end;
end;