




























随着 Embarcadero 8 月 25 号发布 RAD Studio 2009 (Tiburon) 以来(Tiburon 的 RTM 日期可能要延后到 9 - 10 月),随着 Tiburon 全面支持 Unicode,现有的 Delphi / C++ Builder 项目要迁移到 Unicode 下应该注意些什么也成为大家最为关心的问题。Tiburon 对 Unicode 的支持不仅仅是将原来 类型映射为 AnsiString 的 String 类型直接改成 WideString,而是对 AnsiString 结构作出修改,同时增加了 UnicodeString 类型来完美支持 Unicode。这意味着,要想平稳迁移到 Unicode 下,程序员不得不对现有代码作出一定的修改。
在 Tiburon 以前的版本中,AnsiString 和 WideString 除了 data size 不同外,在功能上是相同的。早先版本的 AnsiString 的结构如下:
Format of AnsiString Data Type
|
Reference Count |
Length |
String Data (Byte sized) |
Null Term |
|
-8 |
-4 |
0 |
Length |
而 WideString 类型在早先的版本中用来保存双字节数据。其本质和 Windows BSTR 是一样的。在 Tiburon 中 WideString 仍然是为 COM 保持兼容的,也就是说它依然没有引用计数,相比较而言,UnicodeString 在性能和效率上将会是 COM 以外的程序首选的字符类型。
闪亮登场的 UnicodeString 类型
Tiburon 中,新的、默认的 string 就是 UnicodeString。这个类型既可以包含 ANSI 字符,也可以包含 Unicode 字符。下面是 UnicodeString 类型的结构:
Format of UnicodeString Data Type
|
CodePage |
Element Size |
Reference Count |
Length |
String Data (element sized) |
Null Term |
|
-12 |
-10 |
-8 |
-4 |
0 |
Length * elementsize |
用 Object Pascal 语言来描述 UnicodeString 的结构,应该是这样:
type
UnicodeString 增加了 code page 字段和 element size 来描述字符串内容,这使得 UnicodeString 和其它类型的字符串可以很好的相兼容,所以 AnsiString 和 UnicodeString 可以很方便的互相转换,唯一要注意的是,当把 UnicodeString 向下转型到 AnsiString 的时候,可能会丢失数据,因此强烈建议你不要这么做。UnicodeString 保存的是 UTF-16 字符。
在旧的环境下,可以使用编译标志 Unicode 来判断编译环境是否支持 UnicodeString,以便您可以在同一套代码中维护不同版本的字符支持环境。编译指令如下:
Delphi 使用:
C++ Builder 使用:
变化概要:
下面的操作将不再依赖字符 Size:
GetModuleFileName:
function ModuleFileName(Handle: HMODULE): string;
var
begin
end;GetWindowText:
function WindowCaption(Handle: HWND): string;
begin
end;
字符串索引:
function StripHotKeys(const S: string): string;
var
begin
end;
依赖字符 Size 的代码结构:
在 Tiburon 中,下列列表中列出的这些函数和特性依赖字符 Size,并且已经包含了一个“轻便”的版本,迁移代码的时候只需要将列出的代码迁移到后面提供的轻便版本即可。
范例:
var
begin
end;
SizeOf 返回的是数组的字节数,而 GetWindowText 的 Counts 参数需要的是字符数,所以这里需要把 SizeOf 换成 Length。
var
begin
end;
由于 Length 返回的是字符数,而 Move 的 Count 参数需要的是字节数,所以应该用 SizeOf 或者 Length(Buf1) * SizeOf(Char) 替换 Length(Buf1)。
调用 Read/ReadBuffer 方法的范例:
var
begin
end;
上面的解决方案依赖于您存储在 Stream 中的字符串的编码格式,更好的读取和转换他们建议使用 TEncoding 类。
调用 Write/WriteBuffer 的范例:
var
begin
end;
上面的解决方案依赖于您要存储进 Stream 中的字符串的编码格式,建议使用 TEncoding 类来更好的对格式进行处理。
范例:
var
begin
end;
Length 返回的是字符数,而 FillChar 的 Count 参数需要的是字节数,所以必须用 SizeOf 替换 Length,或者使用 Length * SizeOf(Char)。
另外,需要注意的是,Tiburon 中 Char 等于 2 个字节,FillChar 填充的时候确是按照 Bytes 来计算的,所以,下面的代码
var
begin
end;
并不是向目标中填充 $09,而是 $0909,要得到正确的数值,应该改写成下面这样:
var
begin
...
end;
由于 GetProcAddres 没有对应的 *W (Unicode) 版本的 API,所以只能使用下面的代码来正确调用它:
procedure CallLibraryProc(const LibraryName, ProcName: string);
var
begin
end;
由于 RegQueryValueEx 函数的 Len 指定的是字节数,而不是字符数,所以 Unicode 版本中它的大小是实际需要大小的 2 倍,所以这样的代码:
Len := MAX_PATH;
if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCESS
then
else
应该换成下面这样:
Len := MAX_PATH * SizeOf(Char);
if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCES
then
else
在 Unicode 版本的 CreateProcess 函数中,其行为和 ANSI 的版本略有不同。Unicode 的 CreateProcessW 会改变参数 lpCommandLine 传入的数据,因此调用 CreateProcess / CreateProcessW 的时候,不可以给 lpCommandLine 赋值常量,或者是一个变量指向的常量,否则函数会抛出 access violations 的异常。下面是错误的代码:
// 传入了一个 string 常量
CreateProcess(nil, 'foo.exe', nil, nil, False, 0,
// 传入了一个常量表达式
// 传入了一个引用计数为 -1 的字符串:
const
var
早先的版本中 LeadBytes 常量包含了本地系统中所有可以作为双字节字符 LeadByte 的列表,常常有这样的代码:
if Str[I] in LeadBytes then
现在你需要将它改成调用 IsLeaderChar 函数
if IsLeadChar(Str[I]) then
当您需要用 TMemoryStream 写入一个文本文件的时候,最好在写入任何字符数据进去之前先写入一个 Byte Order Mark (BOM):
var
begin
而任何写入的字符需要被转换成 UTF-8 编码:
var
begin
调用 Windows API MultiByteToWideChar 函数可以简单的用一个任务替代,下面是一个是用 MultiByteToWideChar 的例子:
procedure TWideCharStrList.AddString(const S: string);
var
begin
end;
转换到 Unicode 下可以写作这样(同时支持 Unicode 和 ANSI 字符):
procedure TWideCharStrList.AddString(const S: string);
{$IFNDEF UNICODE}
var
{$ENDIF}
begin
{$IFDEF UNICODE}
{$ELSE}
{$ENDIF}
end;
AppendStr 函数已经废弃了,因为它与 AnsiString 硬编码在一起,而且没有 Unicode 的版本可以替换,所以下面的代码
AppendStr(String1, String2);
应该换成:
String1 := String1 + String2;
您也可以使用新的 TStringBuilder 类来替换。
现有 Delphi 代码中使用了 Named Threads 的代码必须修改了。在早先的版本中,当你需要在分类(gallery)中用一个新的 Thread Object 去创建一个 Thread 的时候,需要在新的 Thread 单元中建立下面的类型:
type
TThreadNameInfo = record
end;
在调试器中,Named Thread 的处理器期待 FName 成员是 ANSI 字符,不是 Unicode,所以上面的声明必须改成下面这样:
type
TThreadNameInfo = record
end;
在新版本中上述声明已经修改,提示这段代码是需要您注意早先版本中您手工创建并声明的代码需要您自己修改。
如果您需要在 Named Thread 中使用 Unicode 字符,您必须将字符串格式化成 UTF-8 编码,调试器可以完全支持改编码。例如:
ThreadNameInfo.FName := UTF8String('UnicodeThread_фис');
注意:C++ Builder 里面一直使用的是正确的代码,所以上述问题在 C++ Builder 中并不存在。
在 Tiburon 更早的版本中,并不是所有的指针类型都支持指针运算。因为这样,为了让无类型指针也支持指针运算,许多代码都将其转化成 PChar 操作。现在,可以使用 Tiburon 中的新编译条件 {$POINTERMATH} 来指示编译器允许指针运算,特别是允许 PByte 的指针运算。{$POINTERMATH ON/OFF} 可以打开/禁止对任意指针变量的运算,增减指针实际操作的是指针元素的大小。
下面的例子是一个将某类型指针转换成 PChar 后的指针运算:
function TCustomVirtualStringTree
begin
end;
您应该将其修改成 PByte 而不是 PChar:
function TCustomVirtualStringTree
begin
end;
在上面的例子中,Node 真实的数据不是 PChar 的数据。将其强制转换成 PChar 的操作在早先的版本中是正常的,因为早先版本中
SizeOf(Char) == Sizeof(Byte)。但是现在不同了,所以这样的代码必须从 PChar 改换成 PByte。如果不做这样的更改,返回的 Pointer 将指向错误的数据。如果你的代码中有使用 TVarRec 类型去处理开放数组的话,你可能需要为其添加对 vtUnicodeString 的支持。参看下列示例:
procedure RegisterPropertiesInCate
var
I: Integer;
begin
代码中包含上述写法的地方可能需要修改以适应 UnicodeString 的变化。
您可能需要修改下列类型:
您需要检查下列可能引起错误的结构:
BOM 必须添加到文件中以便判断文件的编码方式。
本文基于 Tiburon 帮助编写,如有翻译错误或描述不准确的地方欢迎大家指正!相信这次 Delphi / C++ Builder 2009 将是广大爱好者最喜欢的版本之一。
全文完。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。