4

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

Community
  • 1
  • 1
mjn
  • 36,362
  • 28
  • 176
  • 378
  • 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 Apr 25 '14 at 07:53
  • 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 Apr 25 '14 at 07:58
  • 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 Apr 25 '14 at 08:29
  • Why not just use the exception mechanism? You only need to call this once so there are no speed issues here. – Johan Apr 25 '14 at 09:07

3 Answers3

2

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.

Community
  • 1
  • 1
xmojmr
  • 8,073
  • 5
  • 31
  • 54
  • 1
    Fileno assumes libc C centric cooked I/O (FILE *). Free Pascal implements its own, Pascal cooked I/O based on handle based system functions. The equivalent there is Getfilehandle(), but that merely returns the handle from the textrec/filerec, and thus reduces somewhat to Stefan's solution, with the same limitations (assuming the standard descriptors are initialized when not used). – Marco van de Voort Apr 26 '14 at 15:39
1

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.

Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • On Windows I think you'd just call GetStdHandle to test if there was an output device attached. On the Unix platforms you'd call the equivalent API. – David Heffernan Apr 25 '14 at 14:39
  • 1
    And I think the question was about a platform independent way. – Stefan Glienke Apr 25 '14 at 16:24
  • Platform specific code will be needed to do this well – David Heffernan Apr 25 '14 at 16:28
  • 1
    And that would be? The function as I wrote it is free of any platform dependent code. – Stefan Glienke Apr 25 '14 at 16:57
  • Since the filedescriptors are global variables, I think this requires either that the BSS is nilled on startup (which is afaik a Windows feature, not universal!) or that the RTL also initialized the descriptors when not used (iow application compiled for GUI mode). So checking this should only be done under {$ifdef console} I guess. – Marco van de Voort Apr 26 '14 at 15:46
  • @MarcovandeVoort What facts lead you to these statements? Have you read the source in System.pas to come to that opinion? Because they are initialized without any ifdef and used on both Windows and Unix. Also it would be very new to me that global variables are only initialized on Windows and not on other OS since it is documented behavior of Delphi. – Stefan Glienke Apr 26 '14 at 19:26
  • 1
    I can be mistaken. I only thought that getmem returning initialized memory was documented. Afaik only automated global variables are inited. If you know where it is in the documentation, don't hesitate to mention it. It is always hard to distinguish Delphi the language from Delphi the language implementation. – Marco van de Voort Apr 28 '14 at 08:08
  • @MarcovandeVoort Barry is as good (if not better in most parts) as the documentation ;) See http://stackoverflow.com/a/861178/587106 – Stefan Glienke Apr 28 '14 at 09:14
-2

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;
Johan
  • 74,508
  • 24
  • 191
  • 319
  • 1
    This is destructive. It results in output appearing on the console. Presumably one would prefer a test that had no side effects. – David Heffernan Apr 25 '14 at 09:52
  • 1
    `Write('')` so there's no side effect? – Sertac Akyuz Apr 25 '14 at 13:54
  • 2
    `Write('')` does not trigger any IO error because there is nothing to do. – Stefan Glienke Apr 25 '14 at 16:24
  • Worse, it might call a system function with an arbitrary handle as argument. The troubles caused by that might not be recoverable. Rule of thumb: if something can give an access violation or similar "corruption" system error, then don't try to recover, but revert to failsafe and abort. – Marco van de Voort Apr 26 '14 at 15:40