上篇部落格中主要說了GDAL庫C#版本中存在的問題,其表現形式主要是:“檔案名稱中的漢字個數是偶數,完全沒有影響,讀取和建立都正常,如果檔案名稱中的漢字個數是奇數,讀取和建立都會報錯。”
針對這個問題,今天仔細研究(查看+折騰)了下C#程式中string類型的預設編碼方式。首先通過下面的代碼進行檢測C#程式中string類型的預設編碼方式是什麼。
static void Main(string[] args){ string s = "我"; // 首先擷取預設編碼的位元組及其長度,並輸出 byte[] bDefault =Encoding.Default.GetBytes(s); Console.WriteLine(bDefault.Length); foreach (byte b inbDefault) { Console.WriteLine(b); } // 接下來擷取Unicode編碼的位元組及其長度,並輸出 byte[] bUnicode =Encoding.Unicode.GetBytes(s); Console.WriteLine(bUnicode.Length); foreach (byte b inbUnicode) { Console.WriteLine(b); } // 接下來擷取UTF8編碼的位元組及其長度,並輸出 byte[] bUTF8 =Encoding.UTF8.GetBytes(s); Console.WriteLine(bUTF8.Length); foreach (byte b inbUTF8) { Console.WriteLine(b); } // 最後擷取936編碼(即GB2312)的位元組及其長度,並輸出 byte[] b936 =Encoding.GetEncoding(936).GetBytes(s); Console.WriteLine(b936.Length); foreach (byte b in b936) { Console.WriteLine(b); }}
在XP64位中文作業系統與Win764位英文作業系統上運行過上面的程式碼片段,我們通過查看四個byte數組中的值,分別如所示。上面是十進位顯示,下半部分為十六進位顯示。
從可以清楚的看出,C#中對於漢字的預設編碼應該使用的是GB2312(936)編碼。這個預設的編碼與作業系統也沒有關係。
知道了系統預設的漢字編碼方式,那麼下面就來看看昨天的問題,使用代碼System.Text.Encoding.Default.GetString(System.Text.Encoding.UTF8.GetBytes(utf8_path))轉換後的編碼到底變成了什麼編碼。使用下面的程式碼片段進行測試:
static void Main(string[] args){ string s = "我"; string strTEmp = System.Text.Encoding.Default.GetString(System.Text.Encoding.UTF8.GetBytes(s)); byte[] bDefault =Encoding.Default.GetBytes(strTEmp); Console.WriteLine(bDefault.Length); foreach (byte b inbDefault) { Console.WriteLine(b); } Console.WriteLine(strTEmp);}
通過監視查看發現通過上面的代碼轉換之後的位元組與UTF8前兩個一致,但是第三個已經變成了ASCII碼中的63即問號“?”,但是系統當前認為字串依舊是GB2312的編碼,所以字串出現了亂碼。如所示。
接下來我們再看看這個字串通過swig封裝之後,傳入GDAL庫之後的C++語言對應的位元組編碼是什麼,使用前兩篇部落格中的跨語言調試方式,直接將上面的字串“我”用Ogr.Open函數開啟,然後在C++庫中的檔案gdal-1.10.0\port\cpl_vsil_win32.cpp中的函數VSIVirtualHandle*VSIWin32FilesystemHandler::Open( const char *pszFilename, const char *pszAccess )處添加斷點來查看傳入的字串,如所示:
轉換前後的字串及其位元組碼如下:
通過對比這個圖與上面C#的位元組碼,發現了一個問題。C#中的bDefault位元組碼是(230、136、63)轉換為16進位為(0xe6、0x88、0x3f)與C++庫中傳入的位元組碼一致(pszFilename)。這也就是說,通過swig封裝並傳入C++庫後,編碼保持不變,依舊是那個錯誤的編碼。也就是說通過代碼System.Text.Encoding.Default.GetString(System.Text.Encoding.UTF8.GetBytes(utf8_path))進行轉換造成了編碼錯誤,那麼只需要修改這裡,不讓他進行轉碼或者在將Default改成UTF8應該就行了。
按照這個思路,將swig\csharp目錄下的所有的System.Text.Encoding.Default.GetString(System.Text.Encoding.UTF8.GetBytes(utf8_path))都改成System.Text.Encoding. UTF8.GetString(System.Text.Encoding.UTF8.GetBytes(utf8_path))進行測試。
共需改動的檔案不多,共有四個檔案,如所示:
修改時,使用批量替換即可。修改完重新編譯GDAL的C#庫,然後將編譯好的八個dll重新加入工程中,按照上面的步驟再次進行調試。進入C++的代碼中,監視編碼轉換前後的值的變化。如所示。
從可以看出,這裡傳入的字串pszFilename的編碼已經變成了(0xce、0xd2),這個編碼就是C#裡面的預設編碼或者GB2312編碼,也就不是UTF8編碼了。那麼我們就可以通過設定GDAL_FILENAME_IS_UTF8=NO來進行讀取了。
接下來,我們使用一個原來不能開啟的路徑,然後將GDAL_FILENAME_IS_UTF8設定為NO,進行測試。調試代碼如所示,可以看出,原來不能開啟的shp已經正常開啟。
控制台輸出的資訊為:
經過測試,這樣修改可以支援所有的中文及其英文路徑了。測試環境為Xp64位中文作業系統與Win764位英文作業系統。
我已經將修改後的C#版本的8個dll打包上傳至CSDN資源和qq群共用,直接替換之前GDAL110版本裡面的原來的dll即可。CSDN為:http://download.csdn.net/detail/liminlu0314/5809463