4
votes

For newer Delphi versions, with OSX and Android support, is there a platform-independent way to detect if Writeln to Output can be used safely?

The documentation for Output contains a note saying

Most processes do not have a standard output file, and writing to Output raises an error. Delphi programs have a standard output file if they are linked as console applications.

My primary goal is to have a platform-independent fallback for logging, but to avoid any OS errors which can arise when no console (stdout) is present.

For example: would it be sufficient to check IsConsole like so:

procedure Log(const Msg: string);
begin
  if LoggingFrameworkAvailable then
  begin
    // use the logging framework to output the log message
  end if System.IsConsole then
  begin
    // fallback to stdout logging
    WriteLn(Msg);
  end;
end;

So the question could be rephrased: "Can a Delphi application always safely use Output if IsConsole is True?".

As it is meant to be a fallback log method, it would be fine for me if log messages are "invisible" (redirected to /dev/null), as long as the code is guaranteed to run cross-platform without errors.

If yes, does this code also work safely with Free Pascal? (See Can a Windows GUI program written in Lazarus create a console and write to it at runtime?)

3
IsConsole will return false for GUI apps that attach to consoles. Perhaps you need an extra layer of indirection. Allow the client of your code to supply an output device to which your code writes.David Heffernan
I don't see any point in writing app for Android with console support. Logging should be done by writing to a log file and not to stdout.ElmoVanKielmo
I also wonder whether the code is only going to be compiled into programs that you control. Because if it is library code then the consumer of the library might get upset if your library code starts writing on stdout.David Heffernan
Why not just use the exception mechanism? You only need to call this once so there are no speed issues here.Johan

3 Answers

2
votes

Not a final answer, but write {$IFDEF} platform dependant calls to platform independant POSIX based C API function

int fileno (FILE *stream)

..This function returns the file descriptor associated with the stream stream. If an error is detected (for example, if the stream is not valid) or if stream does not do I/O to a file, fileno returns -1

...

There are also symbolic constants defined in unistd.h for the file descriptors belonging to the standard streams stdin, stdout, and stderr...

STDOUT_FILENO .. This macro has value 1, which is the file descriptor for standard output.

STDERR_FILENO .. This macro has value 2, which is the file descriptor for standard error output.

So if the platform independant request for fileno for stream that corresponds to Console output returns 2 or 1 then you are not redirected, if it returns -1 then your output has no end

Exact code will probably be different for Delphi and Free Pascal and Virtual Pascal and GNU Pascal. Go take a look at the runtime libraries for target platforms of your interest, e.g.

1
votes

After digging through the System.pas I came up with this solution:

function CanWriteln: Boolean;
begin
{$IFOPT I+}
  {$DEFINE IOCHECK_ON}
  {$I-}
{$ENDIF}
  if TTextRec(Output).Mode <> fmClosed then
    Result := True
  else
  begin
    Rewrite(Output);
    Result := IOResult = 0;
  end;
{$IFDEF IOCHECK_ON}
  {$I+}
{$ENDIF}
end;

Only tested on Windows with different settings ({$APPTYPE CONSOLE}, "Generate console application" setting, AllocConsole) but in all cases it worked correct.

-2
votes

I would utilize the exception mechanism.

Something like this:

type
  trilean = (dunno, yes, no);

  TLogger = class(TSomething)
  private
    class var FConsoleIsSafe: trilean;
    function GetConsoleIsSafe: boolean;
  public
    property ConsoleIsSafe: boolean read GetConsoleIsSafe; 
  ....

implementation

function TLogger.GetConsoleIsSafe: boolean;
begin
  if (FConsoleIsSafe = dunno) then try
    WriteLn('test'); 
    FConsoleIsSafe:= yes;
  except
    FConsoleIsSafe:= no;
  end;
  Result:= (FConsoleIsSafe = yes);
end;