开发者

Why are changes to an Exception object lost when re-raising it?

I was sure this used to work for me, and I've seen it out on the net (Jolyon Smith and David Moorhouse). Having just tried it in a simple program both in D2007 and 开发者_开发知识库in XE2 trial, it doesn't keep the modified Message. As soon as the "raise" happens, the message reverts back to the original exception.

What blindlingly obvious thing am I missing ? The alternative is to "raise Exception.Create(...)" but I want to just propogate the original exception back up the chain, only with additional information tagged along at each exception block.

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;


Well blow me! This looked so wrong that I had to try it myself, and you're absolutely right! I've narrowed it down to the fact that this is an OS exception (divide by zero) that is generated by the OS itself and not by Delphi. If you try and raise an EIntError yourself you get the expected behaviour, and not what you see above. Note that expected behaviour occurs whenever you raise an exception yourself.

Update: In the System.pas unit there is the following code called when the exception is re-raised:

{ 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

So if the exception is not a Delphi exception (in this case an OS exception) then the (modified) "Delphi" exception is freed and the original exception is re-raised, thereby throwing away any changes made to the exception. Case closed!

Update 2: (couldn't help myself). You can reproduce this with the following code:

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);

Have fun!


See Misha's explanation. As a workaround, you can do this:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜