Независим от платформата начин за проверка дали Writeln to Output може да се използва безопасно?

За по-новите версии на Delphi, с поддръжка на OSX и Android, има ли независим от платформата начин за откриване дали Writeln към Output може да се използва безопасно ?

Документацията за Output съдържа бележка, която казва

Повечето процеси нямат стандартен изходен файл и записът в Output поражда грешка. Програмите на 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 е True?".

Тъй като е предназначен да бъде резервен метод за регистриране, би било добре за мен, ако съобщенията за регистрационни файлове са "невидими" (пренасочени към /dev/null), стига кодът да се изпълнява между платформи без грешки.

Ако да, този код работи ли безопасно и с Free Pascal? (Вижте Може ли GUI програма на Windows, написана на Lazarus, да създаде конзола и да пише в нея по време на изпълнение?)


person mjn    schedule 25.04.2014    source източник
comment
IsConsole ще върне false за GUI приложения, които се прикачват към конзоли. Може би имате нужда от допълнителен слой индиректност. Позволете на клиента на вашия код да предостави изходно устройство, на което вашият код пише.   -  person David Heffernan    schedule 25.04.2014
comment
Не виждам смисъл да пиша приложение за Android с конзолна поддръжка. Регистрирането трябва да се извършва чрез запис в лог файл, а не в stdout.   -  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)

..Тази функция връща файловия дескриптор, свързан с потока на потока. Ако бъде открита грешка (например, ако потокът не е валиден) или ако потокът не прави I/O към файл, 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 приема I/O, ориентиран към libc C (ФАЙЛ *). 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('') не задейства никаква IO грешка, защото няма какво да се направи. - person Stefan Glienke; 25.04.2014
comment
По-лошо, може да извика системна функция с произволен манипулатор като аргумент. Проблемите, причинени от това, може да не бъдат възстановени. Основно правило: ако нещо може да доведе до нарушение на достъпа или подобна повредена системна грешка, тогава не се опитвайте да възстановите, а върнете към безотказно и прекъснете. - person Marco van de Voort; 26.04.2014