Unicode in RAD Studio

經過黃叉叉的唆使,我也在家裏裝上了Delphi2010,一般情況下編譯問題不大,但是好多從以前的工程轉過來的項目上就有些問題了,經常性的問 題就是Unicode的問題!於是網絡Google一番,在Delphi的官方站點上發現了Unicode的一些說明,於是大致的翻譯記錄一下,原文地 址:http://docwiki.embarcadero.com/RADStudio/en/Unicode_in_RAD_Studio

 

在RAD Studio中,基於ANSI -字符串改爲基於Unicode字符串:現在的默認情況下,string類型是一個Unicode字符串(UnicodeString )。這個主題將會告訴你如何更好的去處理字符串。

RAD Studio現在完全支持Unicode標準,但是在您的某些涉及到字符串操作部分的代碼將會需要一些變動,雖然已經花了很多功夫以使用戶進行最低限度的 變化!這是新的數據類型的介紹,現有的數據類型和功能仍和以往一樣。根據公司內部的Unicode轉換的經驗,現有的開發應用程序應該比較順利遷移。

現有的String類型

   預先存在的數據類型 AnsiString WideString 功能與以前一樣的方式。

   注意:ShortString最多包含255個字符,字符爲單字節數據,且只有一個字符計數,不包含編碼頁(code page)信息。對於特定應用,ShortString可以包含UTF-8數據,但缺乏普遍性。

此前系統的string實質上是代表AnsiString. 下表是之前AnsiString各字段含義的內存表示格式:

引用計數位置 字符長度位置

字符數據開始位置

控制符/0結尾
偏移-8
偏移-4 偏移0 長度

 

在RAD Studio中,AnsiString格式已經發生改變,新加入了兩個字段(fields),分別是“編碼頁”字段和“元素大小”字段(CodePage and ElemSize)。從而使得AnsiString表示格式與新的UnicodeString類型完全兼容。

 

WideString

WideString之前用於Unicode字符數據。其格式本質上與Windows BSTR完全一樣。 WideString仍舊適用於COM應用。

 

新String類型:UnicodeString

 

     在RAD Studio中,新的默認string類型是UnicodeString類型。

     在Delphi中,Char和Pchar類型分別爲WideChar和PwideChar。

     注意: 2009之前的版本,string代表AnsiString,而Char和Pchar類型分別爲AnsiChar和PansiChar,此一區別,需要特別注意。

在C++中,_TCHAR映射到 一個可選的浮動興致的定義_TCHAR 可以作爲 wchart_t 也可以是 Char

VCL中限制使用的是UnicodeString類型,它不再是單字節或MBCS字符串的字符串值。

現在的UnicodeString的內存存儲格式爲:

代碼頁位置 元素尺寸位置 代碼頁位置 長度位置 字符數據開始位置 Null Term
-12 -10 -8 -4 0 長度*元素尺寸

UnicodeString可用下面的Object Pascal結構來表示:

代碼
type  StrRec  =   record
      CodePage: Word;
      ElemSize: Word;
      refCount: Integer;
      Len: Integer;
      
case  Integer  of
          
1 array [ 0 .. 0 of  AnsiChar;
          
2 array [ 0 .. 0 of  WideChar;
end ;

UnicodeString加入了“編碼頁”和“元素大小”二字段來說明string內容。UnicodeString與所有其它string類型 賦值兼容(is assignment compatible with all other string types)。但是,在AnsiString和UnicodeString之間的賦值仍然會出現“往上轉換”或“朝下轉換”(still do the appropriate up or down conversions)。應注意,不建議將UnicodeString類型賦值給AnsiString類型,這可能導致數據丟失。

還應注意AnsiString也包含了CodePage和ElemSize二字段。

 

UnicodeString在UTF-16裏邊,有如下理由:

UTF-16 與底層操作系統格式匹配。 UTF-16 減少了顯式/隱式的轉換。 調用 Windows API時的性能更高。 操作系統不需與UTF-16作任何轉換。 基本多語言平臺 (BMP) 已包含了目前世界上絕大多數流行語言文字字形,且適合於單個 UTF-16字符 (16 bits)。 Unicode“替換對”(surrogate pairs)與多字節字符集(MBCS)類似,且更可預測,也更標準。 調度 COM接口時,UnicodeString能夠提供與WideString之間的雙向轉換。

 

UTF-16中的字符可以是2或4字節,因此,string中元素的數目不必一定等於其字符數目。如果string中只包含BMP字符,則字串中的字符個數和元素個數一定相等。

 

採用UnicodeString有如下好處:

  • 可以實現字串中的字符“引用計數”(It is reference-counted)。
  • 可以解決以前應用中的遺留問題。
  • 使得AnsiString支持編碼信息(code page),從而消除了隱含類型轉換中潛在的數據丟失問題。
  • 編譯器能夠確保數據在改變之前的正確性。

 

WideString 沒有引用計數,因此UnicodeString 在某些類型的應用中更加靈活、更加高效(WideString 更合適COM)。

索引

   UnicodeString類型的實例可以尋址字符。尋址以1爲基,如同AnsiString一樣。參考下述代碼:

var  C: Char;
    S: 
string ;
    
begin
        ...
        C :
=  S[ 1 ];
        ...
    
end ;

此 種情況下,編譯器必須確保S中的數據具有適當的格式。編譯器產生代碼必須確保對字串元素的賦值具有合適的類型,並通過調用UniqueString函數, 保證實例的唯一性(即,以“1”爲基的引用) 。代碼中,由於字串可能包含Unicode數據,編譯器在尋址字符陣列之前還需調用UniqueString函數。

編譯器條件

  在Delphi及C++Builder中,可以使用條件編譯允許Unicode和非Unicode代碼兼容共存。

Delphi
{$IFDEF UNICODE}
C++Builder
#ifdef _DELPHI_STRING_UNICODE 

 

變化摘要

  • String現在代表UnicodeString而不是AnsiString.
  • Char現在代表WideChar (2字節而非1字節),且是UTF-16 字符。
  • Pchar現在表示PWideChar.
  • C++中,System::String現在代表UnicodeString類。

未變化摘要

  • AnsiString.
  • WideString.
  • AnsiChar, PAnsiChar.
  • WideChar, PWideChar
  • 仍可使用隱含類型轉換。
  • AnsiString 使用用戶活動“編碼頁”。

與字符大小無關的代碼結構

下述操作與字符大小無關:

  • 字符串串聯:

        <string var> + <string var>

        <string var> + <literal>

        <literal> + <literal>

     Concat(<string> , <string>)

  • 標準string函數
  • 字符串運算:

        <string> <comparison_operator> <string>

        CompareStr()

        CompareText()

  • FillChar(<struct or memory>)
    • FillChar(Rect, SizeOf(Rect), #0)
    • FillChar(WndClassEx, SizeOf(TWndClassEx), #0). 注意到WndClassEx.cbSize := SizeOf(TWndClassEx);
  • Windows API
    • API 調用默認爲其WideString ("W") 版本。
    • PChar(<string>) 類型轉換仍具有相同語義。

 

代碼
// GetModuleFileName例子:

function  ModuleFileName(Handle: HMODULE):  string ;
    
var  Buffer:  array [ 0 ..MAX_PATH]  of  Char;
        
begin
            SetString(Result, Buffer, 
                      GetModuleFileName(Handle, Buffer, Length(Buffer)));
        
end ;

// GetWindowText 例子:

function  WindowCaption(Handle: HWND):  string ;
      
begin
          SetLength(Result, 
1024 );
          SetLength(Result, 
                    GetWindowText(Handle, PChar(Result), Length(Result)));
      
end ;

// String字符索引舉例:

function  StripHotKeys( const  S:  string ):  string ;
    
var  I, J: Integer;
    LastChar: Char;
    
begin
        SetLength(Result, Length(S));
        J :
=   0 ;
        LastChar :
=  # 0 ;
        
for  I : =   1   to  Length(S)  do
        
begin
          
if  (S[I]  <>   ' & ' or  (LastChar  =   ' & ' then
          
begin
              Inc(J);
              Result[J] :
=  S[I];
          
end ;
          LastChar :
=  S[I];
    
end ;
    SetLength(Result, J);
end ;

 

 

與“字符大小”相關的代碼結構

 

在些操作確實與字符大小相關。下面函數及特徵列表中也包含了可能的“可移植”版本。可依此重寫自己的代碼,以便於移植,也就是使得你的代碼在AnsiString和UnicodeString變量中都能夠正常運行。

SizeOf(<Char array>) – 改用可移植指令 Length(<Char array>).

Move(<Char buffer>... CharCount) –改用可移植的Move(<Char buffer> ... CharCount * SizeOf(Char)) .

Stream Read/Write -- 改用可移植的 AnsiString, SizeOf(Char) 或 Tencoding類。

FillChar(<Char array>, <size>, <AnsiChar>) -- 改用 *SizeOf(Char) (填充#0時),或用StringOfChar函數。

GetProcAddress(<module>, <PAnsiChar>) – 改用提供的重載函數並改爲 PWideChar.

使用類型轉換或Pchar作指針運算—在文件頂端加入{IFDEF PByte = PChar} (用Pchar作指針運算時時)。或用 {POINTERMATH <ON|OFF>}編譯指令,對所有的類型指針打開(即設爲ON),以便按照“元素大小”來增/減量(increment /decrement)。

字符集合結構

可能需要修改的結構:

  • <Char> in <set of AnsiChar> -- 代碼生成正確 (但>#255 的字符不在集合當中). 編譯器會提出警告:"WideChar reduced in set operations". 根據你的代碼,你可以安全地關閉警告。或者,你可使用函數CharinSet 取而代之。
  • <Char> in LeadBytes – 這是全局性的LeadBytes集合,用於本地MBCS ANSI. UTF-16 仍保留了"lead char"的概念 (#$D800 - #$DBFF 爲高替代,#$DC00 - #$DFFF爲低替代)。想改變之,可使用重載IsLeadChar指令。ANSI 版會檢驗LeadBytes.而WideChar版本只在“高/低替代”(high/low surrogate)時作驗證。
  • 字符分類—使用TCharacter static類。該字符單元提供了一些函數用於分類字符: IsDigit, IsLetter, IsLetterOrDigit, IsSymbol, IsWhiteSpace, IsSurrogatePair, 等等。這些都是基於直接來自Unicode.org的表數據的。

當心這些結構

特別注意下列有問題的代碼結構

類型轉換將類型模糊化: 有疑問的類型轉換 – 產生警告: 直接構造、操作或訪問字串內部結構。有些,比如AnsiString,其內部已經變化,因此是不安全的。建議使用StringRefCount, StringCodePage, StringElementSize 及其它函數來獲取字串信息。

 

運行時庫

重載. 對函數 PChar,已有對應版本PAnsiChar和PWideChar,建議使用適當函數。 SysUtils.AnsiXXXX 函數,如AnsiCompareString: AnsiStrings unit中的AnsiXXXX 函數提供了SysUtils.AnsiXXXX函數相同的功能,但其只適用於AnsiString. AnsiStrings.AnsiXXXX 對AnsiString 能夠給出比SysUtils.AnsiXXXX 函數更好的性能,其原因是它們不進行隱含轉換操作。而後者同時適用於AnsiString和UnicodeString。 Write/Writeln 和 Read/Readln

    繼續轉換到/從ANSI / OEM代碼頁。

    控制檯是主要的ANSI or OEM

    爲以前的應用程序提供更好的兼容性。

    TFDD(文本文件設備驅動程序):

        TTextRec TFileRec .

        文件名是 WideChar ,但是同上所說,數據是ANSI/OEM.

PByte – 用 $POINTERMATH ON聲明。允許陣列尋址及指針運算,如同PAnsiChar. 字串信息函數String information functions:

     StringElementSize 返回實際數據的大小。

     StringCodePage 返回的字符串數據的代碼頁。

     StringRefCount 返回的引用計數。

RTL 提供了幫助函數,可幫助在“編碼頁”和“元素大小”之間進行顯式的轉換。通常開發人員在字符陣列上使用MOVE函數時,並不知道元素大小。但若能確認所有的RValue引用都產生了對RTL的正確調用從而保證了合適的元素大小,問題就得以緩解。

組件和類

  • TStrings: 內部保存有UnicodeString (仍需聲明爲string).
  • TWideStrings (可能被棄用) 未改變。在內部使用WideString (BSTR)。
  • TStringStream

         已被重寫-默認爲默認ANSI編碼的內部存儲。

         Encoding can be overridden.

         考慮使用 TStringBuilder 替代TStringStream去從bits and pieces來構造一個字符串

  • TEncoding

         Defaults to users’ active code page.

         支持 UTF-8.

         支持 UTF-16, UTF-16be,UTF-16le

         Byte Order Mark (BOM) support.

         You can create descendent classes for user-specific encodings.(您可以繼承該類創建自己的特定編碼)

  • 組件流(即DFM 文本文件)

         完全向後兼容

         Stream as UTF-8 only if component type, property or name contains non-ASCII-7 characters.

         String property values are still streamed in “#” escaped format.

         May allow values as UTF-8 as well (open issue).

         Only change in binary format is potential for UTF-8 data for component name, properties, and type name.

 

字節順序標誌(Byte Order Mark )

就在文件中加入“字節順序標誌”(BOM)以表示其編碼:

  • UTF-8 使用 EF BB BF.
  • UTF-16 小端:使用FF FE.
  • UTF-16 大端:使用 FE FF.

 

Steps to Unicode-enable your applications

用戶需要進行如下步驟:

  1. 檢查(所有) char-和 string-相關函數.
  2. 重建應用.
  3. 檢查替代符號對(Review surrogate pairs).
  4. 檢查string 有效性/安全性(payloads).

 

新增Delphi 編譯警告

 

New warnings have been added to the Delphi編譯程序新增了類型轉換(如由UnicodeString或WideString朝下轉換成AnsiString或AnsiChar)相關的 可能錯誤警告。當將應用轉換到Unicode時,應該使能警告1057和1058,以支持在代碼中發現問題區域。

  • 1057 隱含從'%s'到'%s'的類型轉換 (IMPLICIT_STRING_CAST) ,當編譯程序檢測到必須將AnsiString (或AnsiChar) 隱式地轉換爲某種Unicode(UnicodeString 字串或WideString字串) 形式時發出該警告。(注意:該警告最終將會被默認使能).
  • 1058 從'%s'到'%s'隱含字串類型轉換可能有數據丟失。 (IMPLICIT_STRING_CAST_LOSS) 當編譯程序檢測到必須將某種Unicode(UnicodeString 字串或WideString字串) 形式隱式地轉換爲AnsiString (或 AnsiChar)字串時發出該警告。這是一種潛在的損失性轉換,這是因爲有些字符,無法在目標串的編碼頁中表示出來。(注意: 注意:該警告最終將會被默認使能).
  • 1059:略
  • 1060:略
  • 保持源文件爲UTF-8 格式.
  • 當代碼必須是AnsiString或AnsiChar時,在 IDE中進行重構(refactoring)。 (代碼仍舊可移植code is still portable).
  • Static 代碼檢查:
  • 留意所有警告(乃至錯誤):
  • 確認代碼意圖

 

建議

保存源文件中的UTF - 8格式:

    Delphi 2005, 2006, 2007都支持.

    文件仍然能夠以Ansi的形式編譯(可以使用codepage 編譯器開關).

    Write a UTF-8 BOM to source file. Make sure your source control management system supports these files (most do).

Perform IDE refactoring when code must be AnsiString or AnsiChar (code is still portable). 靜態代碼審查:
  • Is code merely passing the data along?
  • Is code doing simple character indexing?
注意所有的警告(elevate to errors):
  • 可疑的指針類型轉換。
  • 隱顯視轉換
明確代碼的目的 
  • 代碼是否將字符串作爲一個動態數組使用,如果是,使用TBytes類型代替它
  • 一個Pchar能否被轉換成Pointer進行指針運算?如果可以轉換成PByte類型,替代它,同時打開編譯器開關$POINTERMATH ON
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章