RAD Studio 2009 (代號 Tiburon) 引入全面 Unicode 支援,預設字串類型從 AnsiString 改為 UnicodeString (UTF-16)。這是 Delphi 歷來最大型的斷層式變更,現有專案遷移需系統性處理字串、指標、API 呼叫、資料流與結構體對齊等問題。
| 類型 | 舊版 (≤2007) | 新版 (≥2009) | 備註 |
|---|---|---|---|
string | AnsiString | UnicodeString | 預設字串類型變更,影響所有未明確指定類型的字串變數 |
Char | AnsiChar (1 byte) | WideChar (2 bytes) | 字元大小翻倍 |
PChar | PAnsiChar | PWideChar | 指標類型同步變更 |
AnsiString | 預設字串 | 保留,結構擴充 | 新增 CodePage、ElemSize 欄位,與 UnicodeString 結構對齊 |
WideString | BSTR 相容 | 不變 | 無引用計數,COM 專用 |
type
StrRec = record
CodePage: Word; // 新增:字碼頁
ElemSize: Word; // 新增:元素大小 (1=Ansi, 2=Wide)
refCount: Integer; // 引用計數
Len: Integer; // 字元長度 (非位元組數)
case Integer of
1: array[0..0] of AnsiChar;
2: array[0..0] of WideChar;
end;
關鍵點:Length() 回傳字元數,SizeOf() 回傳位元組數,兩者在 UnicodeString 下不再相等。
// 錯誤:假設 Char = 1 byte
SetLength(S, 100);
FillChar(S[1], Length(S), 0); // 僅清除前 100 bytes,應為 200 bytes
// 正確
FillChar(S[1], Length(S) * SizeOf(Char), 0);
// 或
FillChar(Pointer(S)^, Length(S) * SizeOf(Char), 0);
// 錯誤:Length 為字元數,Stream 需位元組數
Stream.Write(Pointer(S)^, Length(S));
// 正確 Unicode 寫法
Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char));
// 正確 Ansi 寫法 (明確編碼)
var AnsiBuf: AnsiString;
AnsiBuf := S; // 隱含轉換使用 Default 字碼頁
Stream.Write(Pointer(AnsiBuf)^, Length(AnsiBuf)); // AnsiChar = 1 byte
// 最佳實務:使用 TEncoding
Stream.Write(TEncoding.UTF8.GetBytes(S), Length(TEncoding.UTF8.GetBytes(S)));
VCL 現在預設呼叫 Unicode 版 API (尾碼 W)。PChar 對應 PWideChar。
// 正確範例:GetModuleFileName
function ModuleFileName(Handle: HMODULE): string;
var
Buffer: array[0..MAX_PATH] of Char; // Char = WideChar
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;
// 錯誤:SizeOf(Buffer) 為位元組數,API 需字元數
Count := SizeOf(Buffer);
GetWindowText(Handle, Buffer, Count);
// 正確
Count := Length(Buffer); // 字元數
// 指標運算:改用 PByte 而非 PChar
// 舊碼
Result := PChar(Node) + Offset;
// 新碼
Result := PByte(Node) + Offset; // 位元組偏移
// 或開啟指標運算
{$POINTERMATH ON}
Result := Pointer(NativeUInt(Node) + Offset);
// 舊碼:填充 #9 (Tab)
FillChar(Buf, Length(Buf), #9); // 實際寫入 $0909 (兩個 WideChar)
// 正確:使用 StringOfChar
StrPCopy(Buf, StringOfChar(#9, Length(Buf)));
// RegQueryValueEx 的 lpcbData 為位元組數
Len := MAX_PATH * SizeOf(Char);
if RegQueryValueEx(Key, PChar(Name), nil, nil, @Data[0], @Len) = ERROR_SUCCESS then
SetString(Result, Data, Len div SizeOf(Char) - 1);
CreateProcessW 會修改 lpCommandLine 緩衝區內容,不可傳入常數字串或引用計數 -1 的字串。
// 錯誤
CreateProcess(nil, 'cmd.exe', ...);
CreateProcess(nil, PChar(ConstStr), ...);
// 正確:使用可寫入緩衝區
var CmdLine: string;
CmdLine := 'cmd.exe';
CreateProcess(nil, PChar(CmdLine), ...);
// 舊碼
if Str[I] in LeadBytes then ...
// 新碼:使用 IsLeadChar (支援 ANSI/Unicode 多載)
if IsLeadChar(Str[I]) then ...
// 字元分類:使用 TCharacter 靜態類別
uses Character;
if TCharacter.IsDigit(C) then ...
if TCharacter.IsSurrogatePair(High, Low) then ...
{$IFDEF UNICODE}
// Unicode 專用碼
FName := PWideChar(S);
{$ELSE}
// Ansi 專用碼
FName := PAnsiChar(AnsiString(S));
{$ENDIF}
// C++Builder
#ifdef _DELPHI_STRING_UNICODE
// Unicode
#endif
TStrings 內部儲存 UnicodeStringTStringStream 重寫為內部儲存 ANSI,建議改用 TStringBuilder 或指定 TEncodingTEncoding 提供 UTF-8/UTF-16/BOM 支援,TEncoding.Default 對應系統作用字碼頁UnicodeString、AnsiString、UTF8String 明確宣告,避免裸 stringUnicode 遷移是一次性投資,長期收益包括:原生支援多國語言、消除字碼頁轉換 Bug、符合現代作業系統與資料庫預期。循序漸進、善用編譯器診斷、建立回歸測試,是成功遷移的關鍵三要素。