Unicode in RAD Studio

RAD Studio完全兼容于Unicode,因此,2009以前的涉及string处理的代码需要作些改动。RAD Studio同时引入了一些新的数据类型,但仍然保留现有的一些数据类型,其功能也完全一样。RAD Studio努力保证了用户现有应用程序的平稳移植,并使改动最小。
现有String类型
AnsiString和WideString以及ShortString用法和以前完全一样。注意:ShortString最多包含255个字符,字符为单字节数据,且只有一个字符计数,不包含编码页(code page)信息。对于特定应用,ShortString可以包含UTF-8数据,但缺乏普遍性。
此前系统的string实质上是代表AnsiString. 下表是之前AnsiString各字段含义的内存表示格式:
Reference Count
Length
String Data (Byte sized)
Null Term
-8
-4
0
Length
在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,而CharPchar类型分别为AnsiCharPansiChar,此一区别,需要特别注意
 
UnicodeString数据类型:
CodePage
Element Size
Reference Count
Length
String Data (element sized)
Null Term
-12
-10
-8
-4
0
Length * elementsize
 
 
 
 
UnicodeString可用下面的Object Pascal结构来表示:

 

代码
type StrRec = record

      CodePage: Word;

      ElemSize: Word;

      refCount: Integer;

      Len: Integer;

      
case Integer of

          
1array[0..0of AnsiChar;

          
2array[0..0of 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中的字符可以是24字节,因此,string中元素的数目不必一定等于其字符数目。如果string中只包含BMP字符,则字串中的字符个数和元素个数一定相等。
采用UnicodeString有如下好处:
  • 可以实现字串中的字符“引用计数”(It is reference-counted)
  • 可以解决以前应用中的遗留问题。
  • 使得AnsiString支持编码信息(code page),从而消除了隐含类型转换中潜在的数据丢失问题。
  • 编译器能够确保数据在改变之前的正确性。
WideString 中的字符是不能“引用计数”的,因此UnicodeString在其它 类型的应用中更加灵活、更加高效。
 
 
字符寻址Indexing
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 串接:
  • 标准string函数
  • 运算符号:
  • 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,已有对应版本PAnsiCharPWideChar,建议使用适当函数。
  • SysUtils.AnsiXXXX 函数,如AnsiCompareString:
  • AnsiStrings unit中的AnsiXXXX 函数提供了SysUtils.AnsiXXXX函数相同的功能,但其只适用于AnsiString. AnsiStrings.AnsiXXXX AnsiString 能够给出比SysUtils.AnsiXXXX 函数更好的性能,其原因是它们不进行隐含转换操作。而后者同时适用于AnsiStringUnicodeString
  • Write/Writeln Read/Readln
  • PByte – $POINTERMATH ON声明。允许阵列寻址及指针运算,如同PAnsiChar.
  • 字串信息函数String information functions:
  • RTL 提供了帮助函数,可帮助在“编码页”和“元素大小”之间进行显式的转换。通常开发人员在字符阵列上使用MOVE函数时,并不知道元素大小。但若能确认所有的RValue引用都产生了对RTL的正确调用从而保证了合适的元素大小,问题就得以缓解。
组件和类
  • TStrings: 内部保存有UnicodeString (仍需声明为string).
  • TWideStrings (可能被弃用) 未改变。在内部使用WideString (BSTR)
  • TStringStream
  • TEncoding
  • 组件流(DFM 文本文件)
字节顺序标志
就在文件中加入“字节顺序标志”(BOM)以表示其编码:
  • UTF-8 使用 EF BB BF.
  • UTF-16 小端:使用FF FE.
  • UTF-16 大端:使用 FE FF.
用户程序需要使能Unicode
用户需要进行如下步骤:
  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 格式.
  • 当代码必须是AnsiStringAnsiChar时, IDE中进行重构(refactoring) (代码仍旧可移植code is still portable).
  • Static 代码检查:
  • 留意所有警告(乃至错误):
  • 确认代码意图
posted @ 2010-01-22 10:43  庄园  阅读(2083)  评论(2编辑  收藏  举报