考虑这个程序:
{$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,或者它本身是否残缺?
答案 0 :(得分:27)
只需使用代码页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)。
更新
上述内容适用于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ЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ');
答案 1 :(得分:11)
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
以及它如何与经典的Pascal I / O进行交互持怀疑态度。但是,它似乎表现为输出到控制台所需的行为。
AlternateWriteUnicodeStringProc
的文档目前正在说,等待它,......
Embarcadero Technologies目前没有任何其他信息。请使用“讨论”页面帮助我们记录此主题!
答案 2 :(得分:5)
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 I / O使用WriteFile
调用。
另请注意,在文件而非控制台上使用时,它也必须正常工作:
unicode text file output differs between XE2 and 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
中正常工作。它只是将输出流写入文件中。
MSDN recommends使用GetConsoleMode
查看标准句柄是否为控制台句柄,还提到了BOM:
如果WriteConsole与标准句柄一起使用,则会失败 重定向到文件。如果应用程序处理多语言输出 可以重定向,确定输出句柄是否为a 控制台句柄(一种方法是调用GetConsoleMode函数和 检查是否成功)。如果句柄是控制台句柄,请调用 WriteConsole。如果句柄不是控制台句柄,则输出为 重定向,你应该调用WriteFile来执行I / O.务必 使用字节顺序标记为Unicode纯文本文件添加前缀。更多 信息,请参阅使用字节订单标记。