Writeln能够支持Unicode吗?

考虑这个程序:

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

我的控制台上使用Consolas字体的输出是:

 ????????ž??????????????????????????????????????

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可以说服尊重统一Writeln ,或者是固有的残废?

只需通过带有代码页cp_UTF8SetConsoleOutputCP()例程来设置控制台输出代码页。

 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ЗИІКЛМНОПҀРСТȢѸФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ'); 

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文档目前说,等待它,…

英巴卡迪诺科技目前没有任何额外的信息。 请使用讨论页面帮助我们记录这个话题!

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调用。

另外请记住,当在一个文件而不是控制台上使用时,它也必须工作:

XE2和Delphi 2009的unicode文本文件输出有所不同?

这意味着,盲目使用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) (或者文件将被编码!)。 此外,PowerShell将始终使用前导码将输出转换为UTF-16。

MSDN建议使用GetConsoleMode找出标准句柄是否是控制台句柄,同时还提到了BOM:

如果将WriteConsole与重定向到文件的标准句柄一起使用,WriteConsole将失败。 如果应用程序处理可以重定向的多语言输出,请确定输出句柄是否是控制台句柄(一种方法是调用GetConsoleMode函数并检查它是否成功)。 如果句柄是控制台句柄,则调用WriteConsole。 如果句柄不是控制台句柄,则输出将被重定向,您应该调用WriteFile来执行I / O。 请务必在Unicode纯文本文件前添加一个字节顺序标记。 有关更多信息,请参阅使用字节顺序标记。