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 与重定向到文件的标准句柄一起使用,它会失败。如果应用程序处理了可以重定向的多语言输出,判断输出句柄是否为控制台句柄(一种方法是调用GetConsoleMode函数并检查是否成功)。如果句柄是控制台句柄,则调用 WriteConsole。如果句柄不是控制台句柄,则输出被重定向,您应该调用 WriteFile 来执行 I/O。确保在 Unicode 纯文本文件前加上字节顺序标记。有关详细信息,请参阅使用字节顺序标记。