2014-10-08 103 views
23

考虑这个程序:Writeln能够支持Unicode吗?

{$APPTYPE CONSOLE} 

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

它采用索拉字体我的控制台上的输出是:

 
????????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

['可能重复](http://stackoverflow.com/q/265018/960757)?我认为['TOndrej的答案'](http://stackoverflow.com/a/268202/960757)涵盖了你的问题。 – TLama 2014-10-08 10:56:24

+1

@TLama我看到了这个问题。我认为这是不同的。我想知道是否有办法使Writeln尊重Unicode。也许通过RTL函数调用开关行为。 – 2014-10-08 10:59:13

+0

只是提示:http://www.bobswart.nl/Weblog/Blog.aspx?RootId=5:3011。另外:http://edn.embarcadero。com/article/39022 – 2014-10-08 11:28:18

回答

25

只需通过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)内部。


更新:

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

Delphi版本? – kludg 2014-10-08 12:42:23

+0

@ user246408,在XE5和XE7中测试。 – 2014-10-08 12:57:14

+0

@ user246408我在XE3中测试了这个。这是一个体面的解决方法(+1),但我会担心更改代码页。至少我希望在进程从控制台分离时将其改回。 – 2014-10-08 12:57:46

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工作正常。它只是将输出流写入文件不变。

但是,PowerShell希望在输出开始时包含ANSI输出或正确的前导码(/ BOM)到(或文件将被编码!)。另外,PowerShell将始终使用前导码将输出转换为UTF-16。

使用GetConsoleMode,以找出是否标准手柄是一个控制台手柄MSDN recommends,也BOM中提到:如果它是一个标准的句柄是 重定向到一个文件中使用

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

+0

-1这不是'WriteConsoleW'的作用。 Windows控制台完全可以通过'WriteConsoleW'编写国际字符,但仅限于UCS-2。向我的问题中的第二个程序添加一个对'Writeln(GetConsoleCP)'的调用,并观察输出结果不是65001.很抱歉让你失望,但是我感觉不得不这样做,因为你所说的是明显错误的。 – 2014-10-08 12:34:38

+0

当您调用'WriteConsoleW'时,前者适用。试试这个:SetConsoleOutputCP(1252); WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),PChar(Text),Length(Text),NumWritten,nil);'请注意,即使输出代码页中不存在字符,文本也会正确输出。 – 2014-10-08 12:39:17

+0

这就够了。 'WriteConsoleW'显然在做重要的工作。 – 2014-10-08 12:57:25

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以及如何将经典帕斯卡尔I/O交互。但是,它看起来像输出到控制台所期望的那样。

AlternateWriteUnicodeStringProc文档目前说,等待它,...

Embarcadero Technologies公司目前还没有任何其他信息。请使用讨论页面帮助我们记录此主题!

+0

在Delphi XE中不起作用 – kludg 2014-10-08 12:59:58

+0

@ user246408您可以扩展吗?什么在XE中不起作用? XE中不存在AlternateWriteUnicodeStringProc吗? – 2014-10-08 13:03:21

+0

@ user246408 D2010'_WriteUString'开始// // !!! FIXME'并没有提及'AlternateWriteUnicodeStringProc',所以我想这就是你所指的 – 2014-10-08 13:05:43