在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
现有 Delphi 项目迁移到 Tiburon 中的注意事项
随着 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 下,程序员不得不对现有代码作出一定的修改。 Format of AnsiString Data Type
Format of UnicodeString Data Type
未变化概要:
由于这些变化,代码编写上也出现了一些值得注意的情况,特别是在你打算将旧有的项目迁移到 Tiburon
下时更是如此。下面就列出一些发生的变化情况以及编写代码时应该注意的注意事项。
范例:
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; 字符串索引: 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;
范例:
var Count: Integer; Buffer: array[0..MAX_PATH - 1] of Char; begin // 现有代码 - 当 string = UnicodeString 的时候这段代码是错的 Count := SizeOf(Buffer); GetWindowText(Handle, Buffer, Count); // 正确的应该是下面这样 Count := Length(Buffer); // <<-- Count 应该是 Chars 而不是 Bytes GetWindowText(Handle, Buffer, Count); end; SizeOf 返回的是数组的字节数,而 GetWindowText 的 Counts 参数需要的是字符数,所以这里需要把 SizeOf 换成 Length。
var
Count: Integer; Buf1, Buf2: array[0..255] of Char; begin // 现有代码 - 当 string = UnicodeString (char = 2 bytes) 时,下面的代码是错误的 Count := Length(Buf1); Move(Buf1, Buf2, Count); // 正确的写法应该是 Count := SizeOf(Buf1); // <<-- Specify buffer size in bytes Count := Length(Buf1) * SizeOf(Char); // <<-- Specify buffer size in bytes Move(Buf1, Buf2, Count); end; 由于 Length 返回的是字符数,而 Move 的 Count 参数需要的是字节数,所以应该用 SizeOf 或者 Length(Buf1) * SizeOf(Char) 替换 Length(Buf1)。
调用
Read/ReadBuffer 方法的范例:
var S: string; L: Integer; Stream: TStream; Temp: AnsiString; begin // 现有代码- 当 string = UnicodeString 时它是不正确的 Stream.Read(L, SizeOf(Integer)); SetLength(S, L); Stream.Read(Pointer(S)^, L); // 正确的 Unicode 写法如下 Stream.Read(L, SizeOf(Integer)); SetLength(S, L); Stream.Read(Pointer(S)^, L * SizeOf(Char)); // <<-- Specify buffer size in bytes //正确的 Ansi 写法如下 Stream.Read(L, SizeOf(Integer)); SetLength(Temp, L); // <<-- 使用临时的变量 AnsiString Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar)); // <<-- Specify buffer size in bytes S := Temp; // <<-- 放宽 string 到 Unicode end; 上面的解决方案依赖于您存储在 Stream 中的字符串的编码格式,更好的读取和转换他们建议使用 TEncoding 类。 调用 Write/WriteBuffer 的范例: var S: string; Stream: TStream; Temp: AnsiString; begin // 现有代码 - 当 string = UnicodeString 时它是错的 Stream.Write(Pointer(S)^, Length(S)); // 正确的读取 Unicode 的代码 Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Specify buffer size in bytes // 正确的读取 Ansi 的代码 Temp := S; // <<-- Use temporary AnsiString Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- Specify buffer size in bytes end; 上面的解决方案依赖于您要存储进 Stream 中的字符串的编码格式,建议使用 TEncoding 类来更好的对格式进行处理。
范例:
var Count: Integer; Buffer: array[0..255] of Char; begin // 现有代码 - 当 string = UnicodeString ( char = 2 字节) 时,这段代码是错的 Count := Length(Buffer); FillChar(Buffer, Count, 0); // 正确的代码应该写作下面这样 Count := SizeOf(Buffer); // <<-- Specify buffer size in bytes Count := Length(Buffer) * SizeOf(Char); // <<-- Specify buffer size in bytes FillChar(Buffer, Count, 0); end; Length 返回的是字符数,而 FillChar 的 Count 参数需要的是字节数,所以必须用 SizeOf 替换 Length,或者使用 Length * SizeOf(Char)。 另外,需要注意的是,Tiburon 中 Char 等于 2 个字节,FillChar 填充的时候确是按照 Bytes 来计算的,所以,下面的代码 var Buf: array[0..32] of Char; begin FillChar(Buf, Length(Buf), #9); end; 并不是向目标中填充 $09,而是 $0909,要得到正确的数值,应该改写成下面这样: var Buf: array[0..32] of Char; begin StrPCopy(Buf, StringOfChar(#9, Length(Buf))); ... end;
由于 GetProcAddres 没有对应的 *W (Unicode)
版本的 API,所以只能使用下面的代码来正确调用它:
procedure CallLibraryProc(const LibraryName, ProcName: string); var Handle: THandle; RegisterProc: function: HResult stdcall; begin Handle := LoadOleControlLibrary(LibraryName, True); @RegisterProc := GetProcAddress(Handle, PAnsiChar (AnsiString (ProcName))); end;
由于 RegQueryValueEx 函数的
Len
指定的是字节数,而不是字符数,所以 Unicode 版本中它的大小是实际需要大小的 2 倍,所以这样的代码:
Len := MAX_PATH; if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCESS then SetString(Result, Data, Len - 1) // Len includes #0 else RaiseLastOSError; 应该换成下面这样: Len := MAX_PATH * SizeOf(Char); if RegQueryValueEx(reg, PChar(Name), nil, nil, PByte(@Data[0]), @Len) = ERROR_SUCCES then SetString(Result, Data, Len div SizeOf(Char) - 1 ) // Len includes #0, Len contains the number of bytes else RaiseLastOSError;
在 Unicode 版本的 CreateProcess
函数中,其行为和 ANSI 的版本略有不同。Unicode 的 CreateProcessW 会改变参数 lpCommandLine 传入的数据,因此调用 CreateProcess /
CreateProcessW 的时候,不可以给
lpCommandLine
赋值常量,或者是一个变量指向的常量,否则函数会抛出
access
violations 的异常。下面是错误的代码:
// 传入了一个 string 常量 CreateProcess(nil, 'foo.exe', nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo); // 传入了一个常量表达式 const cMyExe = 'foo.exe' CreateProcess(nil, cMyExe, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo); // 传入了一个引用计数为 -1 的字符串: const cMyExe = 'foo.exe' var sMyExe: string; sMyExe := cMyExe; CreateProcess(nil, PChar(sMyExe), nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
早先的版本中 LeadBytes
常量包含了本地系统中所有可以作为双字节字符 LeadByte 的列表,常常有这样的代码:
if Str[I] in LeadBytes then 现在你需要将它改成调用 IsLeaderChar 函数 if IsLeadChar(Str[I]) then
当您需要用 TMemoryStream
写入一个文本文件的时候,最好在写入任何字符数据进去之前先写入一个 Byte Order Mark (BOM):
var Bom: TBytes; begin ... Bom := TEncoding.UTF8.GetPreamble; Write(Bom[0], Length(Bom)); 而任何写入的字符需要被转换成 UTF-8 编码: var Temp: Utf8String; begin ... Temp := Utf8Encode(Str); // Str 是要写入文件的字符 Write(Pointer(Temp)^, Length(Temp)); //Write(Pointer(Str)^, Length(Str)); 原来写入字符串的代码
调用 Windows API MultiByteToWideChar
函数可以简单的用一个任务替代,下面是一个是用 MultiByteToWideChar 的例子:
procedure TWideCharStrList.AddString(const S: string); var Size, D: Integer; begin Size := SizeOf(S); D := (Size + 1) * SizeOf(WideChar); FList[FUsed] := AllocMem(D); MultiByteToWideChar(0, 0, PChar(S), Size, FList[FUsed], D); Inc(FUsed); end; 转换到 Unicode 下可以写作这样(同时支持 Unicode 和 ANSI 字符): procedure TWideCharStrList.AddString(const S: string); {$IFNDEF UNICODE} var L, D: Integer; {$ENDIF} begin {$IFDEF UNICODE} FList[FUsed] := StrNew(PWideChar(S)); {$ELSE} L := Length(S); D := (L + 1) * SizeOf(WideChar); FList[FUsed] := AllocMem(D); MultiByteToWideChar(0, 0, PAnsiChar(S), L, FList[FUsed], D); {$ENDIF} Inc(FUsed); end;
AppendStr 函数已经废弃了,因为它与 AnsiString
硬编码在一起,而且没有 Unicode 的版本可以替换,所以下面的代码
AppendStr(String1, String2); 应该换成: String1 := String1 + String2; 您也可以使用新的 TStringBuilder 类来替换。
现有 Delphi 代码中使用了 Named Threads
的代码必须修改了。在早先的版本中,当你需要在分类(gallery)中用一个新的 Thread
Object
去创建一个 Thread 的时候,需要在新的
Thread 单元中建立下面的类型:
type TThreadNameInfo = record FType: LongWord; // must be 0x1000 FName: PChar; // pointer to name (in user address space) FThreadID: LongWord; // thread ID (-1 indicates caller thread) FFlags: LongWord; // reserved for future use, must be zero end; 在调试器中,Named Thread 的处理器期待 FName 成员是 ANSI 字符,不是 Unicode,所以上面的声明必须改成下面这样: type TThreadNameInfo = record FType: LongWord; // must be 0x1000 FName: PAnsiChar ; // pointer to name (in user address space) FThreadID: LongWord; // thread ID (-1 indicates caller thread) FFlags: LongWord; // reserved for future use, must be zero 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.InternalData(Node: PVirtualNode): Pointer; begin if (Node = FRoot) or (Node = nil) then Result := nil else Result := PChar(Node) + FInternalDataOffset; end; 您应该将其修改成 PByte 而不是 PChar: function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer; begin if (Node = FRoot) or (Node = nil) then Result := nil else Result := PByte(Node) + FInternalDataOffset; end; 在上面的例子中,Node 真实的数据不是 PChar 的数据。将其强制转换成 PChar 的操作在早先的版本中是正常的,因为早先版本中 SizeOf(Char) == Sizeof(Byte)。但是现在不同了,所以这样的代码必须从 PChar 改换成 PByte。如果不做这样的更改,返回的 Pointer 将指向错误的数据。
如果你的代码中有使用 TVarRec
类型去处理开放数组的话,你可能需要为其添加对 vtUnicodeString
的支持。参看下列示例:
procedure RegisterPropertiesInCategory(const CategoryName: string; const Filters: array of const); overload; var I: Integer; begin if Assigned(RegisterPropertyInCategoryProc) then for I := Low(Filters) to High(Filters) do with Filters[I] do case vType of vtPointer: RegisterPropertyInCategoryProc(CategoryName, nil, PTypeInfo(vPointer), ); vtClass: RegisterPropertyInCategoryProc(CategoryName, vClass, nil, ); vtAnsiString: RegisterPropertyInCategoryProc(CategoryName, nil, nil, string(vAnsiString)); vtUnicodeString: RegisterPropertyInCategoryProc(CategoryName, nil, nil, string(vUnicodeString)); else raise Exception.CreateResFmt(@sInvalidFilter, [I, vType]); end; end;
代码中包含上述写法的地方可能需要修改以适应 UnicodeString
的变化。
应当心这些结构
控件和类
Byte Order Mark
做好这些注意事项,将帮助您顺利地把旧有项目迁移到 Tiburon 的 Unicode
下。当然,如果您开发的是多版本控件,或者是希望项目能在多个版本中编译,您最好根据这些特性定义适当的编译条件,以便让代码更好的被更低的版本的编译器
支持和编译。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论