How to return an error code with Halt(n) from an Exception block with D2007?
Asked Answered
K

5

6

Update: It seems to be specific to D2007. It works in D2010 like it worked in older version.

I would like to return an exit code depending on the type of Exception caught in the Eception Handler block like:

program test;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Exitcode: Integer;
begin
  Writeln('Enter error code:');
  Readln(Exitcode);
  try
    raise EExternal.Create('sdsdkfjh');
  except
    on E:EExternal do
    begin
      Writeln(E.Classname, ': ', E.Message);
      Halt(Exitcode);
    end;
  end;
end.

Unfortunately in D2007, calling Halt(n) from an Exception block always returns an Exit code 1, no matter what you pass to Halt().

Apparently because exiting from an Exception handler calls Finalize, which clears the pending (non Abort) Exceptions, calling SysUtils.ExceptHandler:

procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer); far;
begin
  ShowException(ExceptObject, ExceptAddr);
  Halt(1); // <= @#$##@#$!
end;

And no matter what exit code I wanted I get that Halt(1)!

So the question is:
How can I simply return the desired Exit code depending on which Exception was raised?

Keyes answered 4/8, 2010 at 19:56 Comment(4)
Based on the comment below by Mike, which is true, it indeed does return the correct errorcode. I suspect it's the method which you use to get the ErrorCode that might not work as expected.Instructive
@Aldo. No, it's D2007. The same exact thing behaves differently with D2007 and D2010 where it's back as I expected and reported by Mike.Keyes
Please file a big report in QC (qc.embarcadero.com); although there probably won't be a D2007 upgrade, it is nice to be able to see there which bugs are 'known'.Isolecithal
@Jeroen Pluimers The bug report is already here - see my answer.Sextant
S
5

Will this work?

NeedHalt := False;
try
  raise EExternal.Create('sdsdkfjh');
except
  on E:EExternal do
  begin
    Writeln(E.Classname, ': ', E.Message);
    NeedHalt := True;
  end;
end;
if NeedHalt then
  Halt(Exitcode); 

Or this?

try
  raise EExternal.Create('sdsdkfjh');
except
  on E:EExternal do
  begin
    Writeln(E.Classname, ': ', E.Message);
    AcquireExceptionObject;
    Halt(Exitcode); 
  end;
end;

Anyway: it's a bug in D2007, which was fixed in D2010.

Sextant answered 4/8, 2010 at 22:25 Comment(1)
Thanks! +1 for AcquireExceptionObject is doing the trick for me as a workaround in D2007. That was a nice bug indeed...Keyes
G
2

Actually... it seems to work as intended....

I used your code...

program test1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Exitcode: Integer;
begin
  Writeln('Enter error code:');
  Readln(Exitcode);
  try
    raise EExternal.Create('sdsdkfjh');
  except
    on E:EExternal do
    begin
      Writeln(E.Classname, ': ', E.Message);
      Halt(Exitcode);
    end;
  end;
end.

Compiled in in Delphi 5, then ran it in a DOS box under XP...

C:\>test1
Enter error code:
111
EExternal: sdsdkfjh

C:\>echo %errorlevel%
111

C:\>

Note that DOS Error Levels are restricted to the range of 0 to 65535. Echoing %errorlevel% is the quickest way to see the error level.

Don't forget that reading the errorlevel clears it.

Gerhard answered 4/8, 2010 at 20:13 Comment(2)
Does not work in D2007 anymore! But thanks for confirming it used to work! I was pretty much sure I had done it that way before.. ;-)Keyes
And it also works in D2010. The same exact code, the same exact way of testing errorlevel gives me what I want in D2010 like for you with D5, but with D2007, I always get 1!Keyes
D
2

If you want to immediately abort the program without any cleanup, and return an exit code, try ExitProcess. See the article for a few caveats on using ExitProcess, though.

Diplex answered 4/8, 2010 at 20:16 Comment(1)
+1 for ExitProcess. It's a bit too harsh for my present case, but worth to be remembered.Keyes
S
0

Using halt(I) generates memory leaks (you can see that if you enabled the FastMM MemoryLeaks with ReportMemoryLeaksOnShutdown:=true;)

It's much better to use a "Clean" Exit and set ExitCode prior to exiting.

In a main section of a console app for instance:

ExitCode:=I;
exit;
Sink answered 4/3, 2016 at 18:44 Comment(0)
C
-1

If the built-in exception-handling function doesn't do what you like, then replace it with your own:

function ExitCodeExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer);
begin
  ShowException(ExceptObject, ExceptAddr);
  if ExitCode = 0 then
    ExitCode := 1;
  Halt(ExitCode);
end;

Assign that to the global System.ExceptProc variable when your program starts:

ExceptProc := @ExitCodeExceptHandler;

I've implemented it to use the global ExitCode variable. If it's still at its default value of 0, then the function reverts to the original Delphi behavior of exiting with 1, but if the exit code has already been set to something else, then this will halt with that value instead. The first thing Halt does is set the global ExitCode variable, so your code should need no further changes (although I'd choose a different name for the Exitcode variable). Your call to Halt will set the global ExitCode variable and then proceed to shut down the program. The exception handler will notice that ExitCode is already set and re-call Halt with that value instead of 1.

Calash answered 4/8, 2010 at 20:41 Comment(1)
Unfortunately, it does not work. First, it should be a procedure, not a function, but more important when arriving in DoneExceptions, the code resets ExceptProc := nil then calls directly if (ExceptObject <> nil) and not (ExceptObject is EAbort) then ExceptHandler(ExceptObject, ExceptAddr);Keyes

© 2022 - 2024 — McMap. All rights reserved.