Независимый от платформы способ проверить, можно ли безопасно использовать Writeln to Output?

Для более новых версий Delphi с поддержкой OSX и Android существует ли независимый от платформы способ определения наличия Writeln, чтобы можно было безопасно использовать вывод ?

Документация для вывода содержит примечание о том, что

У большинства процессов нет стандартного файла вывода, и запись в вывод вызывает ошибку. Программы Delphi имеют стандартный выходной файл, если они связаны как консольные приложения.

Моя основная цель - иметь платформенно-независимую резервную копию для ведения журнала, но во избежание любых ошибок ОС, которые могут возникнуть при отсутствии консоли (stdout).

Например: достаточно ли проверить IsConsole следующим образом:

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;

Таким образом, вопрос можно перефразировать: «Может ли приложение Delphi всегда безопасно использовать Вывод, если IsConsole истинно? ".

Поскольку он задуман как резервный метод ведения журнала, для меня будет нормально, если сообщения журнала будут «невидимыми» (перенаправлены на / dev / null), если гарантируется, что код будет работать на разных платформах без ошибок.

Если да, то безопасно ли работает этот код с Free Pascal? (См. Может ли программа Windows GUI, написанная на Lazarus, создать консоль и писать в нее во время выполнения?)


person mjn    schedule 25.04.2014    source источник
comment
IsConsole вернет false для приложений с графическим интерфейсом, которые подключаются к консолям. Возможно, вам понадобится дополнительный уровень косвенности. Разрешите клиенту вашего кода предоставить устройство вывода, на которое записывает ваш код.   -  person David Heffernan    schedule 25.04.2014
comment
Не вижу смысла писать приложение для Android с консольной поддержкой. Ведение журнала должно выполняться путем записи в файл журнала, а не в стандартный вывод.   -  person ElmoVanKielmo    schedule 25.04.2014
comment
Мне также интересно, будет ли код компилироваться только в программы, которые вы контролируете. Потому что, если это код библиотеки, потребитель библиотеки может расстроиться, если код вашей библиотеки начнет писать на stdout.   -  person David Heffernan    schedule 25.04.2014
comment
Почему бы просто не использовать механизм исключения? Вам нужно вызвать это только один раз, чтобы здесь не было проблем со скоростью.   -  person Johan    schedule 25.04.2014


Ответы (3)


Не окончательный ответ, но напишите {$ IFDEF} платформенно-зависимые вызовы для независимого от платформы POSIX на основе функция C API

int fileno (FILE *stream)

..Эта функция возвращает дескриптор файла, связанный с потоком потока. Если обнаружена ошибка (например, если поток недействителен) или если поток не выполняет ввод-вывод для файла, fileno возвращает -1

...

Также существуют символические константы, определенные в unistd.h для дескрипторов файлов, принадлежащих стандартным потокам stdin, stdout и stderr ...

STDOUT_FILENO .. Этот макрос имеет значение 1, которое является дескриптором файла для стандартного вывода.

STDERR_FILENO .. Этот макрос имеет значение 2, которое является дескриптором файла для стандартного вывода ошибок.

Поэтому, если независимый запрос платформы для fileno для потока, который соответствует выходу консоли, возвращает 2 или 1, тогда вы не перенаправляетесь, если он возвращает -1, тогда ваш результат не имеет конца

Точный код, вероятно, будет отличаться для Delphi и Free Pascal, Virtual Pascal и GNU Pascal. Взгляните на библиотеки времени выполнения для целевых платформ, которые вас интересуют, например

person xmojmr    schedule 25.04.2014
comment
Fileno предполагает, что ввод-вывод, ориентированный на libc C (FILE *). Free Pascal реализует собственный ввод-вывод, приготовленный на основе Pascal, на основе системных функций на основе дескрипторов. Эквивалент Getfilehandle (), но он просто возвращает дескриптор из textrec / filerec и, таким образом, несколько сводится к решению Стефана с теми же ограничениями (при условии, что стандартные дескрипторы инициализируются, когда не используются). - person Marco van de Voort; 26.04.2014

Покопавшись в System.pas, я пришел к следующему решению:

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;

Проверено только в Windows с разными настройками ({$ APPTYPE CONSOLE}, настройка «Создать консольное приложение», AllocConsole), но во всех случаях все работало правильно.

person Stefan Glienke    schedule 25.04.2014
comment
В Windows я думаю, вы бы просто позвонили GetStdHandle, чтобы проверить, подключено ли устройство вывода. На платформах Unix вы вызываете эквивалентный API. - person David Heffernan; 25.04.2014
comment
И я думаю, что вопрос был о независимом от платформы способе. - person Stefan Glienke; 25.04.2014
comment
Для этого потребуется код, специфичный для платформы. - person David Heffernan; 25.04.2014
comment
А что бы было? Функция, как я написал, не содержит кода, зависящего от платформы. - person Stefan Glienke; 25.04.2014
comment
Поскольку файловые дескрипторы являются глобальными переменными, я думаю, для этого требуется, чтобы либо BSS был обнулен при запуске (что является функцией Windows, а не универсальным!), Либо RTL также инициализировал дескрипторы, когда они не используются (приложение iow, скомпилированное для режима GUI) . Полагаю, проверять это следует только под {$ ifdef console}. - person Marco van de Voort; 26.04.2014
comment
@MarcovandeVoort Какие факты привели вас к этим заявлениям? Вы читали источник в System.pas, чтобы прийти к такому мнению? Потому что они инициализируются без ifdef и используются как в Windows, так и в Unix. Также для меня было бы в новинку, что глобальные переменные инициализируются только в Windows, а не в других ОС, поскольку это задокументированное поведение Delphi. - person Stefan Glienke; 26.04.2014
comment
Я могу ошибаться. Я только думал, что getmem, возвращающий инициализированную память, был задокументирован. Afaik задаются только автоматические глобальные переменные. Если вы знаете, где это находится в документации, не стесняйтесь упоминать об этом. Всегда трудно отличить Delphi как язык от Delphi как язык реализации. - person Marco van de Voort; 28.04.2014
comment
@MarcovandeVoort Barry так же хорош (если не лучше в большинстве частей), как и документация;) См. stackoverflow.com/a/861178/587106 < / а> - person Stefan Glienke; 28.04.2014

Я бы использовал механизм исключения.

Что-то вроде этого:

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;
person Johan    schedule 25.04.2014
comment
Это разрушительно. В результате вывод появляется на консоли. Предположительно, кто-то предпочел бы тест, у которого не было побочных эффектов. - person David Heffernan; 25.04.2014
comment
Write('') так нет побочного эффекта? - person Sertac Akyuz; 25.04.2014
comment
Write('') не вызывает никаких ошибок ввода-вывода, потому что делать нечего. - person Stefan Glienke; 25.04.2014
comment
Хуже того, он может вызвать системную функцию с произвольным дескриптором в качестве аргумента. Проблемы, вызванные этим, не могут быть устранены. Практическое правило: если что-то может привести к нарушению доступа или аналогичной системной ошибке повреждения, не пытайтесь восстановить, а вернитесь в режим отказоустойчивости и прервите работу. - person Marco van de Voort; 26.04.2014