2016-11-22 68 views
-1

我將一些代碼從C移植到C++,並剛剛發現包含em-dash的路徑的問題,例如, 「C:\ TEMP \測試1.dgn」。即使路徑在Visual Studio 2005調試器中正確顯示,對fstream :: open()的調用也會失敗。爲什麼fstream不支持文件名中的em-dash?

奇怪的是,使用C庫fopen()函數的舊代碼工作正常。我想我會用wfstream類試試我的運氣,然後發現使用mbstowcs()轉換我的C字符串完全失去了em-dash,這意味着它也失敗了。

我想這是一個語言環境問題,但爲什麼在默認語言環境中不支持em-dash?爲什麼不能fstream處理一個em-dash?我會認爲文件流類支持Windows文件系統支持的任何字節字符。

鑑於這些限制,處理打開可能包含有效Windows文件名的文件流的正確方法是什麼?

+0

首先,您需要說明您的文件名實際包含哪些字節。將一個emdash(0x2014)傳遞給fopen將在技術上與某些轉換或將其分割爲兩個字節或其他內容,但之後它不會成爲emdash。使用例如。 FindFirstFileW/FindNextFileW等,然後查看原始字節值。 – deviantfan

+0

對不起,應該澄清一點,我只是有一個普通的舊式C風格的單字節字符串,而不是UTF8。 em-dash表示爲單個字節 - 0x97,它應該是標準的ANSI 8位字符。 – Piers

回答

0

發佈此解決方案的其他人遇到此問題。問題在於Windows在默認情況下在啓動時分配「C」語言環境,並且在「Windows-1252」代碼頁中定義了em-dash(0x97),但未在「C」語言環境使用的正常ASCII表中定位。所以簡單的解決方案是打電話:

setlocale (LC_ALL, ""); 

在fstream :: open之前。這將當前的代碼頁設置爲OS定義的代碼頁。在我的程序中,我想用fstream打開的文件是由用戶定義的,所以它在系統定義的代碼頁(Windows-1252)中。

因此,雖然擺弄unicode和寬字符可能是避免未映射字符的解決方案,但它不是問題的根源。實際的問題是輸入字符串的代碼頁(「Windows-1252」)與Windows程序默認使用的活動代碼頁(「C」)不匹配。

0

這應該工作,只要一切你做的是在寬字符符號與寬字符功能。也就是說,使用wfstream,但不是使用mbstowcs,使用與L字符前綴的寬字符串文字:

const wchar_t* filename = L"C:\temp\test—1.dgn"; 

此外,請確保您的源文件保存爲在Visual Studio UTF-8。否則,em-dash可能會在em-dash中出現語言環境問題。

+0

'L'前綴只會在每一個後面添加一個'0x00'byte。如果em-dash字符「-'編碼爲」0x12 0x82「(#8212的小尾數)。 'L' - 「'相當於字節0x12 0x00 0x82 0x00 0x00 0x00,而不是0x12 0x82 0x00 0x00。 – Gonmator

1

字符破折號在UTF-16(在小端0x14 0x20),0xE2 0x80 0x94在UTF-8,並在所有取決於所使用的字符集代碼和頁面上的其他碼或不代碼編碼爲U+2014。 Windows-1252代碼頁(在西歐語言中很常見)具有破折號字符0x97,我們可以認爲它是等效的。

Windows在內部管理UTF-16路徑,因此每次使用其錯誤的ANSI接口(功能以A結尾)調用該函數時,路徑將使用爲用戶配置的當前代碼頁轉換爲UTF-16。

另一方面,C和C++的RTL可以實現訪問「ANSI」或「Unicode」(以W結尾的函數)接口。在第一種情況下,用於表示字符串的代碼頁必須與用於系統的代碼頁相同。在第二種情況下,我們可以從頭開始直接使用utf-16字符串,或者用於轉換爲utf-16的函數必須配置爲使用源字符串的相同代碼頁進行映射。

是的,這是一個複雜的問題。有幾個錯誤(或問題)的建議來解決這個問題:

  • 使用wfstream代替fstreamwfstream做什麼可以與之fstream不同的路徑。沒有。它只是意味着「管理像wchar_t的字節流」。 (它以一種不同的方式做到了這一點,因此在大多數情況下使這個階級變得毫無用處,但這是另一個歷史)。要在Visual Studio實現中使用Unicode接口,它存在重載的構造函數和接受const wchar_t*的函數open()。這些函數和構造函數都被重載爲fstreamwfstream。使用fstream與正確的open()
  • mbstowcs()這裏的問題是語言環境(其中包含字符串中使用的代碼頁)使用。如果由於缺省語言環境匹配系統語言環境而匹配語言環境,那麼酷。如果沒有,你可以試試mbstowcs_l()。但是這些函數是不安全的C函數,所以你必須小心緩衝區大小。無論如何,只有在運行時才能獲得轉換路徑,這種方法纔有意義。如果它是編譯時已知的靜態字符串,最好直接在代碼中使用它。
  • L"C:\\temp\\test—1.dgn"字符串中的L前綴並不意味着「將此字符串轉換爲utf-16」(源代碼使用8位字符),至少在Visual Studio實現中不存在。 L前綴的意思是「在引號之間的每個字符之後添加一個0x00字節」。因此,相當於在窄(普通)字符串中的字節0x97,它在寬(前綴爲L)字符串但不是0x14 0x20時變爲0x97 0x00。相反,它是更好地使用它的通用字符名稱:L"C:\\temp\\test\\u20141.dgn"

一種流行的做法是在代碼中使用UTF-8或UTF-16一直使用,只有在絕對必要時進行轉換。將具有特定代碼頁的字符串轉換爲utf-8或utf-16時,首先嚐試將其轉換爲其中一個(utf-8或utf-16),以標識第一個正確的代碼頁。要做這種轉換,根據它們來自哪裏來使用這些功能。如果你從XML文件中得到你的字符串,那麼通常在那裏解釋使用的代碼頁(並用作utf-8)。如果它來自Windows控件,請使用Windows API函數,如MultiByteToWideChar。 (CP_ACPGetACP()用作默認代碼頁)。

始終使用fstream(而不是wfstream)及其寬接口(open和構造函數),而不是其狹窄的接口。 (您可以再次使用MultiByteToWideChar將utf-8轉換爲utf-16)。

對於這種方法,有幾篇文章和建議。其中之一,我建議你:http://www.nubaria.com/en/blog/?p=289