2014-10-08 16 views
23

Betrachten Sie dieses Programm:Kann Writeln Unicode unterstützen?

{$APPTYPE CONSOLE} 

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

Die Ausgabe auf meiner Konsole, die die Consolas Schriftart verwendet wird:

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

Die Windows-Konsole unterstützen Unicode durchaus in der Lage ist, wie dieses Programm belegt:

{$APPTYPE CONSOLE} 

uses 
    Winapi.Windows; 

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

var 
    NumWritten: DWORD; 

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

, für die die Ausgabe ist:

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

Kann Writeln davon überzeugt werden, Unicode zu respektieren, oder ist es von Natur aus verkrüppelt?

+1

['Mögliches Duplikat '] (http://stackoverflow.com/q/265018/960757)? Ich denke, [Antwort von Tndrej] (http://stackoverflow.com/a/268202/960757) deckt Ihre Frage ab. – TLama

+1

@TLama Ich habe diese Frage gesehen. Ich denke, das ist anders. Ich möchte wissen, ob es einen Weg gibt, Writeln Unicode zu respektieren. Vielleicht durch eine RTL-Funktion das Verhalten der Schalter aufrufen. –

+0

Nur Hinweise: http://www.bobswart.nl/Weblog/Blog.aspx?RootId=5:3011. Außerdem: http: //edn.embarcadero.com/article/39022 –

Antwort

25

Setzen Sie einfach die Codepage der Konsolenausgabe durch die Routine mit 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. 

Ausgänge:

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

WriteLn() Unicode übersetzt UTF16 Zeichenfolgen an die ausgewählte Ausgabecodepage (CP_UTF8) intern.


Update:

Die oben genannten Arbeiten in Delphi-XE2 und höher. In Delphi-XE benötigen Sie eine explizite Konvertierung in UTF-8, damit es ordnungsgemäß funktioniert.

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

Nachtrag:

Wenn eine Ausgabe an die Konsole in einem anderen Zeichensatz vor dem Aufruf SetConsoleOutputCP(cp_UTF8), das OS in utf-8 wird nicht korrekt ausgegeben Text erfolgt. Dies kann durch Schließen/erneutes Öffnen des Stdout-Handlers behoben werden.

Eine andere Option besteht darin, einen neuen Textausgabe-Handler für utf-8 zu deklarieren.

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

Delphi-Version? – kludg

+0

@ user246408, getestet in XE5 und XE7. –

+0

@ user246408 Ich habe das in XE3 getestet. Es ist ein guter Workaround (+1), aber ich hätte Bedenken, die Codepage zu ändern. Zumindest würde ich es wieder ändern wollen, wenn der Prozess von der Konsole getrennt wurde. –

5

WriteConsoleW scheint eine ziemlich magische Funktion zu sein.

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. 

Also zusammenfassend:

WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ...) unterstützt UTF-16.

WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), ...) unterstützt UTF-16 nicht.

Meine Vermutung wäre, dass die klassischen Pascal I/O verwendet, um verschiedene ANSI-Kodierungen zu unterstützen, die WriteFile Aufruf.

Denken Sie auch daran, dass, wenn auf einer Datei statt der Konsole verwendet es als gut zu funktionieren hat:

unicode text file output differs between XE2 and Delphi 2009?

Das bedeutet, dass WriteConsole Pausen Ausgabeumleitung blind verwenden. Wenn Sie WriteConsole verwenden, sollten Sie zurück zu WriteFile wie diese fallen:

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; 

Beachten Sie, dass die Umleitung der Ausgabe mit einer beliebigen Codierung in cmd.exe gut funktioniert. Es schreibt den Ausgabestrom einfach unverändert in die Datei.

PowerShell erwartet jedoch entweder ANSI-Ausgang oder die korrekte Präambel (/ BOM) muss am Anfang der Ausgabe enthalten sein (oder die Datei wird malencoded!). Auch PowerShell konvertiert die Ausgabe immer in UTF-16 mit Präambel.

MSDN recommendsGetConsoleMode mit, um herauszufinden, ob der Standard-Griff eine Konsole Griff ist, auch die Stückliste genannt:

Writeconsole schlägt fehl, wenn es mit einem Standard-Griff verwendet wird, die in eine Datei umgeleitet wird. Wenn eine Anwendung die mehrsprachige Ausgabe verarbeitet, die umgeleitet werden kann, ermitteln Sie, ob das Ausgabehandle ein Konsolenhandle ist (eine Methode besteht darin, die GetConsoleMode-Funktion aufzurufen und zu überprüfen, ob es erfolgreich ist). Wenn das Handle ein Konsolenhandle ist, rufen Sie WriteConsole. Wenn das Handle kein Konsolenhandle ist, wird die Ausgabe umgeleitet und Sie sollten WriteFile aufrufen, um die E/A durchzuführen. Stellen Sie sicher, dass eine Unicode-Textdatei mit einer Byte-Reihenfolge markiert. Weitere Informationen zu Informationen finden Sie unter Verwenden von Byte Order Marks.

+0

-1 Das tut 'WriteConsoleW' nicht. Die Windows-Konsole ist in der Lage, internationale Zeichen über 'WriteConsoleW' zu schreiben, obwohl sie auf UCS-2 beschränkt ist. Fügen Sie einen Aufruf von "Writeln (GetConsoleCP)" zu dem zweiten Programm in meiner Frage hinzu, und beobachten Sie, dass die Ausgabe nicht 65001 ist. Entschuldigen Sie, dass ich Sie abgelehnt habe, aber ich fühlte mich dazu gezwungen, da das, was Sie sagen, nachweislich falsch ist. –

+0

Wenn Sie 'WriteConsoleW' aufrufen, gilt das vorherige. Versuchen Sie Folgendes: 'SetConsoleOutputCP (1252); WriteConsole (GetStdHandle (STD_OUTPUT_HANDLE), PChar (Text), Length (Text), NumWritten, nil); 'Beachten Sie, dass der Text korrekt ausgegeben wird, obwohl die Zeichen in der Ausgabe-Codepage nicht vorhanden sind. –

+0

Das ist wahr genug. 'WriteConsoleW' leistet eindeutig bedeutende Arbeit. –

11

Die System Einheit deklariert eine Variable AlternateWriteUnicodeStringProc benannt, die Anpassung von ermöglicht, wie Writeln Ausgang führt. Dieses Programm:

{$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. 

erzeugt diese Ausgabe:

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

Ich bin skeptisch, wie ich umgesetzt habe MyAlternateWriteUnicodeStringProc und wie es mit den klassischen Pascal I/O in Wechselwirkung treten würde. Es scheint sich jedoch wie gewünscht für die Ausgabe an die Konsole zu verhalten.

Die Dokumentation von AlternateWriteUnicodeStringProc sagt zur Zeit, denn es warten, ...

Embarcadero Technologies haben zurzeit keine zusätzliche Informationen. Bitte helfen Sie uns, dieses Thema auf der Diskussionsseite zu dokumentieren!

+0

Funktioniert auch nicht in Delphi XE – kludg

+0

@ user246408 Können Sie erweitern? Was funktioniert in XE nicht? Ist 'AlternateWriteUnicodeStringProc' nicht in XE vorhanden? –

+0

@ user246408 Der D2010 '_WriteUString' beginnt' // !!! FIXME' und hat keinen Bezug auf 'AlternateWriteUnicodeStringProc' also denke ich, dass das, was Sie beziehen sich auf –

Verwandte Themen