Writeln est-il capable de supporter Unicode?

estime que ce programme est:

{$APPTYPE CONSOLE}

begin
  Writeln('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
end.

la sortie sur ma console qui utilise la police Consolas est:

????????Z??????????????????????????????????????

la console Windows est tout à fait capable de supporter Unicode comme en témoigne ce programme:

{$APPTYPE CONSOLE}

uses
  Winapi.Windows;

const
  Text = 'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ';

var
  NumWritten: DWORD;

begin
  WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(Text), Length(Text), NumWritten, nil);
end.

pour lequel la sortie est:

АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ

peut-on convaincre Writeln de respecter Unicode, ou est-il intrinsèquement infirme?

24
demandé sur David Heffernan 2014-10-08 14:52:46

3 réponses

vient de définir le codepage de sortie de la console par le SetConsoleOutputCP() routine avec codepage cp_UTF8 .

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,Windows;
Const
  Text =  'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ';
VAR
  NumWritten: DWORD;
begin
  ReadLn;  // Make sure Consolas font is selected
  try
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(Text), Length(Text), NumWritten, nil);    
    SetConsoleOutputCP(CP_UTF8);
    WriteLn;
    WriteLn('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

sorties:

АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ
АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ

WriteLn() traduit en interne les chaînes Unicode UTF16 en codepage de sortie sélectionné (cp_UTF8).


mise à Jour:

Les travaux ci-dessus dans Delphi XE2, et au-dessus. Dans Delphi-XE vous avez besoin d'une conversion explicite en UTF-8 pour le faire fonctionner correctement.

WriteLn(UTF8String('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ'));

Addendum:

si une sortie vers la console est effectuée dans une autre page de code avant d'appeler SetConsoleOutputCP(cp_UTF8) , le système d'exploitation ne produira pas correctement le texte de utf-8 . Ceci peut être corrigé en fermant/rouvrant le handler stdout.

une autre option consiste à déclarer un nouveau gestionnaire de sortie de texte pour utf-8 .

var
  toutUTF8: TextFile;
...
SetConsoleOutputCP(CP_UTF8);
AssignFile(toutUTF8,'',cp_UTF8);  // Works in XE2 and above
Rewrite(toutUTF8);
WriteLn(toutUTF8,'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
27
répondu LU RD 2014-10-09 19:31:53

l'unité System déclare une variable nommée AlternateWriteUnicodeStringProc qui permet de personnaliser la façon dont Writeln exécute la sortie. Ce programme:

{$APPTYPE CONSOLE}

uses
  Winapi.Windows;

function MyAlternateWriteUnicodeStringProc(var t: TTextRec; s: UnicodeString): Pointer;
var
  NumberOfCharsWritten, NumOfBytesWritten: DWORD;
begin
  Result := @t;
  if t.Handle = GetStdHandle(STD_OUTPUT_HANDLE) then
    WriteConsole(t.Handle, Pointer(s), Length(s), NumberOfCharsWritten, nil)
  else
    WriteFile(t.Handle, Pointer(s)^, Length(s)*SizeOf(WideChar), NumOfBytesWritten, nil);
end;

var
  UserFile: Text;

begin
  AlternateWriteUnicodeStringProc := MyAlternateWriteUnicodeStringProc;
  Writeln('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
  Readln;
end.

produit ce résultat:

АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ

je suis sceptique quant à la façon dont j'ai implémenté MyAlternateWriteUnicodeStringProc et comment il interagirait avec Pascal I/O. cependant, il semble se comporter comme souhaité pour la sortie vers la console.

la documentation de AlternateWriteUnicodeStringProc actuellement dit, Attendez-le, ...

Amarcadero Technologies ne dispose actuellement d'aucune information supplémentaire. Aidez-nous à documenter ce sujet en utilisant la page de Discussion!

11
répondu David Heffernan 2014-10-08 12:54:20

WriteConsoleW semble être une fonction tout à fait magique.

procedure WriteLnToConsoleUsingWriteFile(CP: Cardinal; AEncoding: TEncoding; const S: string);
var
  Buffer: TBytes;
  NumWritten: Cardinal;
begin
  Buffer := AEncoding.GetBytes(S);
  // This is a side effect and should be avoided ...
  SetConsoleOutputCP(CP);
  WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Buffer[0], Length(Buffer), NumWritten, nil);
  WriteLn;
end;

procedure WriteLnToConsoleUsingWriteConsole(const S: string);
var
  NumWritten: Cardinal;
begin
  WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(S), Length(S), NumWritten, nil);
  WriteLn;
end;

const
  Text = 'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ';
begin
  ReadLn; // Make sure Consolas font is selected
  // Works, but changing the console CP is neccessary
  WriteLnToConsoleUsingWriteFile(CP_UTF8, TEncoding.UTF8, Text);
  // Doesn't work
  WriteLnToConsoleUsingWriteFile(1200, TEncoding.Unicode, Text);
  // This does and doesn't need the CP anymore
  WriteLnToConsoleUsingWriteConsole(Text);
  ReadLn;
end.

donc en résumé:

WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ...) supporte UTF-16.

WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), ...) ne supporte pas L'UTF-16.

je pense que pour supporter différents encodages ANSI le classique Pascal I/O utilise l'appel WriteFile .

gardez également à l'esprit que lorsqu'il est utilisé sur un fichier au lieu de la console il doit fonctionner aussi bien:

la sortie du fichier texte unicode diffère-t-elle entre XE2 et Delphi 2009?

cela signifie que l'utilisation aveugle de WriteConsole interrompt la redirection de sortie. Si vous utilisez WriteConsole vous devez revenir à WriteFile comme ceci:

var
  NumWritten: Cardinal;
  Bytes: TBytes;
begin
  if not WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(S), Length(S),
    NumWritten, nil) then
  begin
    Bytes := TEncoding.UTF8.GetBytes(S);
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Bytes[0], Length(Bytes),
      NumWritten, nil);
  end;
  WriteLn;
end;

notez que la redirection de sortie avec n'importe quel encodage fonctionne très bien dans cmd.exe . Il a juste écrit la flux de sortie vers le fichier inchangé.

PowerShell s'attend toutefois à ce que soit sortie ANSI ou le préambule correct (/BOM) doit être inclus au début de la sortie (ou le fichier sera malencodé!). Aussi PowerShell convertira toujours la sortie en UTF-16 avec préambule.

MSDN recommande en utilisant GetConsoleMode pour savoir si la poignée standard est une poignée de console, aussi le BOM est mentionné:

WriteConsole échoue si elle est utilisée avec une poignée standard qui est redirigée vers un fichier. Si une application traite une sortie multilingue qui peut être redirigé, déterminer si la poignée de sortie est un manette de la console (une méthode consiste à appeler la fonction GetConsoleMode et vérifiez si il y arrive). Si la poignée est une poignée de console, appeler WriteConsole. Si la poignée n'est pas une console de poignée, la sortie est redirigé et vous devrait appeler WriteFile pour effectuer l'entrée / sortie. préfixe Unicode fichier texte avec une marque d'ordre d'octet. Pour plus d' information, voir utilisation des marques D'ordre de Octet.

5
répondu Jens Mühlenhoff 2017-05-23 11:47:26