Lazarus中文社区

 找回密码
 立即注册(注册审核可向QQ群索取)

QQ登录

只需一步,快速开始

Lazarus IDE and 组件 下载地址版权申明
查看: 5822|回复: 3

Lazarus 對於 String 編碼的 BUG 與處理

[复制链接]

该用户从未签到

发表于 2010-8-25 21:51:05 | 显示全部楼层 |阅读模式
分類 : win32 版

Lazarus 0.9.24 以前的版本中,  String 是 ANSI 編碼,一個英文是 1 Byte,一個中文字是 2 Byte。Lazarus 0.9.26 版以後, 開始使用 UTF8 編碼,英文字母佔據的空間不變,中文字佔據的空間將是 3~6 個 Byte 不等。但在某些場合中我捫需要精確計算字串位置與長度, 如發票印表機自己有帶字型, 根據 ANSI STRING "一個英文是 1 Byte,漢字是 2 Byte" 的規則及使用習慣, 傳統上 BCB 5/6 或 Delphi 7 把中文字的 String (ANSI CODE) 丟到 ComPort 即可列印, 但 Lazarus 0.9.28 的 String 是 UTF8 CODE , 若是直接將中文的 String ( UTF8 CODE ) 丟到 ComPort , 會讓發票印表機印出亂碼, 故要特別處理 UTF8 -> ANSI 的 String 轉換

Lazarus 0.9.28 在 LCLProc單元中提供了幾個與 UTF8 字串相關的函數,例如有 Utf8ToAnsi(), AnsiToUtf8,  UTF8Length()、UTF8CharAt() 等等,在一定程度上可以給我們提供幫助 , 我自己也寫了一些公用函式, 來加強 UTF8 與 ANSI 字串編碼的處理

  BUG  (以下都是在 Lazarus 0.9.28 之 WIN32 版本下測試發現的, 其它平台如 Linux 版不一定會有相同狀況)
雖然  Lazarus 0.9.28  已全面使用 UTF8 編碼, 但仍有些函式取回的 String 仍為 ANSI String , 混合處理的情況下會造成許多困擾, 所以在此整理一下

▲ INI FILE
//TIniFile 物件字串的 "讀出值" 及 "讀出預設值" 仍為 ansi, 故要轉換
MyIni:TIniFile;
procedure TForm1.FormCreate(Sender: TObject);
begin
  MyIni := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
  MyPath := ExtractFilePath(Application.ExeName);
  MyPath:=ansitoutf8(MyPath); //改為 UTF8
  MyList := TStringList.Create;
  MyList.Clear;
  tmp:=MyPath+&#39OSDB.FDB'; //路徑可能夾帶中文
  FDB_FN:=ansitoutf8(MyIni.ReadString(&#39ARAMETER', 'FDB_FILENAME', utf8toansi(tmp)));
  ShowMessage(FDB_FN);
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  MyIni.WriteString(&#39ARAMETER', 'FDB_FILENAME', utf8toansi(FDB_FN));
  MyList.Free;
  MyIni.Free;
end;  

▲ ExtractFilePath() 函式 : 取出某檔案的所在路徑
我發現它取出路徑的字串仍是 ANSI String 編碼, 要與一般 String 變數 (default 是 UTF8 編碼) 做 "+" 號處理會有問題, 所以每次取出後要用 AnsiToUtf8() 轉成 UTF8 String

▲ FileExists() 函式 : 判斷檔案存不存在
if (FileExists(Utf8ToAnsi(SDB_FN))) then
begin
    .....
end;                                    

▲ TStringList 物件的主要屬性仍為 Ansi String
//以下為正確處理示範
procedure TForm1.Button1Click(Sender: TObject);
var TmpList: TStringList;
    i:integer;
begin
  TmpList := TStringList.Create;
  TmpList.LoadFromFile(Utf8ToAnsi('C:\新資料夾\新增文字文件.txt'));
  for i:=0 to TmpList.Count-1 do
  begin
    ShowMessage(AnsiToUtf8(TmpList.Strings));
  end;
  TmpList.SaveToFile(Utf8ToAnsi('C:\新資料夾\新增文字文件2.txt'));
  TmpList.Free;
end;         

▲ 某些控件 String 屬性需為 Ansi String
如 要設定資料庫控件 TDbf 的 TableName 屬性, 需傳入 Ansi String
My_Path:=ExtractFilePath(Application.ExeName);  //ExtractFilePath() 取回的是 ANSI
Dbf1.TableName:=My_Path+'myTest.DBF';  //TDbf 的 TableName 可直接餵入 ANSI STRING
而指定另一種資料庫控件 SQLite3Connection1 的 DatabaseName 屬性 卻又須使用標準 UTF8 String
My_Path:=ExtractFilePath(Application.ExeName);  //ExtractFilePath() 取回的是 ANSI
My_Path:=AnsiToUtf8(My_Path); //改為 UTF8
SQLite3Connection1.DatabaseName:=My_Path+'myTest.db';  //指定一個 DATABASE 要餵入 UTF8 STRING
所以以後若指定某控件的字串屬性, 明明是正確的字串內容, 但卻出現編譯錯誤或是控件無法正確動作, 可能就是 String 編碼問題

自己寫的公用函式
//---------------------------------------------------------------------------
//Length(), Copy(), Pos() 傳入 Ansi String 才會準確
//UTF8Length(), UTF8Copy() 傳入 UTF8 String 才會準確
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Length(str: String):Integer; //或稱 AnsiLength()
var tmp:String;
begin
tmp:=UTF8ToAnsi(str);
result:=Length(tmp);
end;
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Copy(str: String; idx,len: integer):String; //或稱 AnsiCopy()
var tmp,tmp2:String;
begin
tmp:=UTF8ToAnsi(str);
tmp2:=Copy(tmp,idx,len);
result:=AnsiToUTF8(tmp2);
end;
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Pos(substr,str: String):Integer; //或稱 AnsiPos()
var tmp,tmp2:String;
begin
tmp:=UTF8ToAnsi(substr);
tmp2:=UTF8ToAnsi(str);
result:=Pos(tmp,tmp2);
end;                  

常用的 String 處理相關函式有 Length(), Copy(), Pos()  等, 都是以 Ansi String 角度來看結果的, 但在 Lazarus 中 utf8 跟 ansi 混用的情況下, 只好  "以UTF8 String 為參數, 而以 Ansi String 為角度(一個英文是 1 Byte,一個中文字是 2 Byte) " 來編寫新的函式
例如
var tmp: String ;  
    tmp2: String ;
begin
  tmp:='字串處理測試函式';  //default 都是 UTF8 編碼的 String
  tmp2:=_Copy(tmp,5,4);  //以UTF8 String 為參數, 而以 Ansi String 為角度, 取出 tmp 中 第 5 byte 起連續 4 個 byte 的字串 , 取回值也為 UTF8 編碼 String
  ShowMessage(tmp2);  //傳入 ShowMessage() 的 String 需為 UTF8, 故 tmp2 可直接傳入
end;  

若要以 Lazarus 內定的 Copy() 取出正確位置的字串, 需要轉來轉去的, 很麻煩
var tmp: String ;
    tmp2: String ;
begin
  tmp:='字串處理測試函式';  //default 都是 UTF8 編碼的 String
  tmp:=Utf8ToAnsi(tmp);  //先把 UTF8 編碼的 String 轉成 Ansi 編碼
  tmp2:=Copy(tmp,5,4);  //用 Lazarus 內建的 Copy() 處理 Ansi String, 取回值也為 Ansi String
  ShowMessage(AnsiToUtf8(tmp2));  //傳入 ShowMessage() 的 String 需為 UTF8, 故需再把 Ansi 再轉回 UTF8
end;

補充說明 : 我個人認為 Lazarus 既然要全面使用 UTF8 String , 就不應該再提供像是 Copy() 等這些需傳入 Ansi String 的函式, 而是應提供像我寫的 _Copy() 這種函式 , 傳入傳出都是 UTF8 String , 但內部是以 Ansi String 的角度 (一個英文是 1 Byte,一個中文字是 2 Byte) 來處理字串

评分

参与人数 1威望 +11 收起 理由
猫工 + 11 原创内容

查看全部评分

回复

使用道具 举报

该用户从未签到

发表于 2011-2-12 23:33:10 | 显示全部楼层
我顶。。文章内容少于 10 个字节。
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-2-18 12:47:35 | 显示全部楼层
UTF8Decode和UTF8Encode两函数没介绍
回复 支持 反对

使用道具 举报

该用户从未签到

发表于 2011-3-20 09:50:03 | 显示全部楼层
为什么fpc不使用UnicodeString来统一字符串呢?既可以存utf8,也可以存utf-16,目前虽然有unicodeString, 但基本上是个摆设, 很多调用unicode版本的winapi都临时转成WideString再调用,影响效率
回复 支持 反对

使用道具 举报

*滑块验证:

本版积分规则

QQ|手机版|小黑屋|Lazarus中国|Lazarus中文社区 ( 鄂ICP备16006501号-1 )

GMT+8, 2025-5-3 10:54 , Processed in 0.034266 second(s), 14 queries , Redis On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表