经过黄叉叉的唆使,我也在家里装上了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的表数据的。
当心这些结构
特别注意下列有问题的代码结构
运行时库
继续转换到/从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).
AnsiString
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
文连接,否则保留追究法律责任的权利。