Может ли Writeln поддерживать Unicode?
Рассмотрим эту программу:
{$APPTYPE CONSOLE}
begin
Writeln('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
end.
Выход на моей консоли, использующей шрифт Consolas:
????????Z??????????????????????????????????????
Консоль Windows вполне способна поддерживать Unicode, о чем свидетельствует эта программа:
{$APPTYPE CONSOLE}
uses
Winapi.Windows;
const
Text = 'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ';
var
NumWritten: DWORD;
begin
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(Text), Length(Text), NumWritten, nil);
end.
для которого выход:
АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ
Можно ли Writeln
убедить уважать Unicode, или он по своей сути искалечен?
Ответы
Ответ 1
Просто установите кодовую страницу выхода консоли через процедуру SetConsoleOutputCP()
с кодовой страницей 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.
Выходы:
АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ
АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ
WriteLn()
преобразует строки Unicode UTF16 в выбранную кодовую страницу вывода (cp_UTF8) внутри.
Update:
Вышеупомянутые работы находятся в Delphi-XE2 и выше.
В Delphi-XE вам нужно явно преобразовать UTF-8, чтобы он работал правильно.
WriteLn(UTF8String('АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ'));
Добавление:
Если вывод на консоль выполняется в другой кодовой странице перед вызовом SetConsoleOutputCP(cp_UTF8)
,
ОС не будет правильно выводить текст в utf-8
.
Это можно устранить, закрыв/снова открыв обработчик stdout.
Другой вариант - объявить новый обработчик вывода текста для utf-8
.
var
toutUTF8: TextFile;
...
SetConsoleOutputCP(CP_UTF8);
AssignFile(toutUTF8,'',cp_UTF8); // Works in XE2 and above
Rewrite(toutUTF8);
WriteLn(toutUTF8,'АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
Ответ 2
Блок System
объявляет переменную с именем AlternateWriteUnicodeStringProc
, которая позволяет настроить способ выполнения Writeln
вывода. Эта программа:
{$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.
производит этот вывод:
АБВГДЕЖЅZЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ
Я скептически отношусь к тому, как я реализовал MyAlternateWriteUnicodeStringProc
и как он будет взаимодействовать с классическим I/O Pascal. Однако, по-видимому, он ведет себя по желанию для вывода на консоль.
Документация AlternateWriteUnicodeStringProc
в настоящее время говорит, дождитесь ее,...
В настоящее время Embarcadero Technologies не располагает никакой дополнительной информацией. Пожалуйста, помогите нам документировать эту тему, используя страницу обсуждения!
Ответ 3
WriteConsoleW
кажется довольно магической функцией.
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.
Итак, вкратце:
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), ...)
поддерживает UTF-16.
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), ...)
не поддерживает UTF-16.
Я предполагаю, что для поддержки различных кодировок ANSI классический ввод/вывод Pascal использует вызов WriteFile
.
Также имейте в виду, что при использовании в файле вместо консоли он должен работать:
Выход текстового файла unicode отличается между XE2 и Delphi 2009?
Это означает, что слепое использование WriteConsole
нарушает перенаправление вывода. Если вы используете WriteConsole
, вы должны вернуться к WriteFile
следующим образом:
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;
Обратите внимание, что перенаправление вывода с любой кодировкой отлично работает в cmd.exe
. Он просто записывает выходной поток в файл без изменений.
Однако PowerShell ожидает, что в начале вывода будет включен либо ANSI-выход, либо правильная преамбула (/BOM), или файл будет malencoded!). Также PowerShell всегда будет преобразовывать вывод в UTF-16 с преамбулой.
MSDN рекомендует с помощью GetConsoleMode
, чтобы узнать, является ли стандартный дескриптор консольным дескриптором, также упоминается спецификация:
Ошибка WriteConsole, если он используется со стандартным дескриптором, который перенаправляется в файл. Если приложение обрабатывает многоязычный вывод которые могут быть перенаправлены, определить, является ли выходной дескриптор консольный дескриптор (один метод - вызвать функцию GetConsoleMode и проверьте, удастся ли это выполнить). Если дескриптор представляет собой консольный дескриптор, вызовите WriteConsole. Если дескриптор не является дескриптором консоли, выход перенаправлен, и вы должны позвонить WriteFile для выполнения ввода-вывода. Будь уверен префикс текстового файла Unicode с байтом порядка байтов. Для большего информацию см. в разделе "Использование байтов".