2014-10-08 67 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