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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章