經過黃叉叉的唆使,我也在家裏裝上了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結構來表示:
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中的字符可以是2或4字節,因此,string中元素的數目不必一定等於其字符數目。如果string中只包含BMP字符,則字串中的字符個數和元素個數一定相等。
採用UnicodeString有如下好處:
- 可以實現字串中的字符“引用計數”(It is reference-counted)。
- 可以解決以前應用中的遺留問題。
- 使得AnsiString支持編碼信息(code page),從而消除了隱含類型轉換中潛在的數據丟失問題。
- 編譯器能夠確保數據在改變之前的正確性。
WideString
沒有引用計數,因此UnicodeString
在某些類型的應用中更加靈活、更加高效(WideString
更合適COM)。
索引
UnicodeString類型的實例可以尋址字符。尋址以1爲基,如同AnsiString一樣。參考下述代碼:
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>) 類型轉換仍具有相同語義。
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(文本文件設備驅動程序):
文件名是 WideChar
,但是同上所說,數據是ANSI/OEM.
StringElementSize
返回實際數據的大小。
StringCodePage
返回的字符串數據的代碼頁。
StringRefCount
返回的引用計數。
組件和類
- 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
用戶需要進行如下步驟:
- 檢查(所有) char-和 string-相關函數.
- 重建應用.
- 檢查替代符號對(Review surrogate pairs).
- 檢查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 beAnsiString
or AnsiChar
(code is still portable).
靜態代碼審查:
- Is code merely passing the data along?
- Is code doing simple character indexing?
- 可疑的指針類型轉換。
- 隱顯視轉換
- 代碼是否將字符串作爲一個動態數組使用,如果是,使用TBytes類型代替它
- 一個Pchar能否被轉換成Pointer進行指針運算?如果可以轉換成PByte類型,替代它,同時打開編譯器開關
$POINTERMATH ON