Delphi 7编译的程序任何系统都正常显示

Delphi 7编译的程序任何系统都正常显示[这个没提供解决方案,可当基础知识]

字符编码的问题。
字符编码在Delphi7中已经得到了很大提高。
Delphi7自己的IDE虽然不能读取Unicode编码的源代码文件,但编译器已经支持
AnsiString和WideString的转换。也就是说,只要定义的时候定义

Technorati 标签:

WideString,
那么在后面直接给他赋值时,AnsiString自动转换为WideString,反之亦然。
这样有好处也有坏处,好处是在快速开发中,不需要考虑更多的字符转换问题,
能够比较平顺地从Win98向NT字符集转换,坏处是混淆了字符界限,深入看下
去,有时候搞不清我的内存里究竟是Ansi还是Wide,特别是希望仅仅使用宽字
符的情况下,更要留意字符格式的定义。
WideString保存为文本文件时,常用的有UTF-8、Unicode、Ansi、Unicode Big Endian,
其中 UTF-8 的格式,从文件读取的时候,需要利用 Delphi7 提供的 Utf8ToUnicode
转换一下全部编码,其他几种编码本身都不需要转换(BigEndian编码是摩托罗拉规范,是
intel 规范的 Unicode (即我们现在说的 WideString)编码的字符按字节反转,这符合摩
托罗拉生产的计算机芯片的构造特点,所以读取后要按 WORD 反转),但保存为相应格式的
文本文件时,必须按要求在文件头部写入一个编码识别记号,他们分别为:
Ansi:不需要
Unicode:$FEFF (十六进制编辑器看到的是高位在前显示$FFFE,以下同)
BigEndian:$FFFE (正好是上面 Unicode 的反转)
UTF-8:$BBEF $BF (三字节,十六进制编辑器里显示 $EFBB BF)
这样,其他编辑器读取时就可以识别出保存者把文本翻译成了什么编码。
Unicode(即WideString)只要写好文件头,后面的就按照保存Ansi文本一样把
文本写入文件,保存为Big Endian,则按WORD逐字节反转写入,保存为UTF-8
要利用UnicodeToUtf8转换后写入。
在XML解析中,如果带有非ASCII编码的文字,MS默认使用UTF-16编码,如果
原始文本是Ansi编码,这时将获得乱码的字符。这个编码不是Delphi造成的,是
MS的XML库所致,所以在使用非ASCII字符前,建议转换成UTF-8编码,上面例
子中我没有使用WideString,所以没有实现编码转换。
编码转换有很多现成的开源代码可以利用,其中影响最深远的就是JEDI的Unicoee.pas,
但这个文件很庞大,大约有250K大小,它还带有一个转换表的资源文件,如果
处理一些小型的字符转换就显得杀鸡用牛刀了。当然我们可以直接利用Delphi7
提供给我们的函数,比如:
function PUCS4Chars(const S: UCS4String): PUCS4Char;
function WideStringToUCS4String(const S: WideString): UCS4String;
function UCS4StringToWidestring(const S: UCS4String): WideString;
function UnicodeToUtf8(Dest: PChar; Source: PWideChar; MaxBytes: Integer): Integer;
function UnicodeToUtf8(Dest: PChar; MaxDestBytes: Cardinal; Source: PWideChar; SourceChars: Cardinal): Cardinal;
function Utf8ToUnicode(Dest: PWideChar; Source: PChar; MaxChars: Integer): Integer;
function Utf8ToUnicode(Dest: PWideChar; MaxDestChars: Cardinal; Source: PChar; SourceBytes: Cardinal): Cardinal;
function Utf8Encode(const WS: WideString): UTF8String;
function Utf8Decode(const S: UTF8String): WideString;
function AnsiToUtf8(const S: string): UTF8string;
function Utf8ToAnsi(const S: UTF8string): string;
等等。这些已经足够使用了。轻量级的代码是OmniXML中的TGpTextStream,
不过这个代码有不少BUG,并且不支持BigEndian的写入(读取部分也因忘了使
用临时变量而错误)。这些都可以利用。
在Delphi7中,Edit等控件不支持WideString,但有一组TnTWare的开源控件可
以直接支持WideString。
所以,了解了这些内容后,就可以明确这么多编码在读入内存后变成了什么。
读入内存中的字符其实已经只剩下二种格式了:
要么是 AnsiString,
要么是WideString。
因此,对于认识字符编码的关键就是理解读取和理解保存,只有这二个地方需
要对编码有了解才能正确地完成工作。
哦,对了,还要补充一下Delphi中比较特殊的一个事情:本来我们全程使用了
WideString后,在NT系统下应该可以不考虑处于哪种语言环境的,但是Delphi
的全部控件都是基于Ansi的,因此,除非使用了象Tnt控件一样的显示控件,
否则都要注意字符集的定义。象Edit,如果要显示WideString,Edit的Line.Text
会自动转换为AnsiString,这个转换的依据是活动文档的键盘定义或者活动文档
的字符集定义(字符集定义优先),因此一定不要忘记把Edit字符集设置为与
文本相适应的标志,比如中文,就设置为GB2313_CHARSET,这样,转换时会
使用936的中文字符集。这个设置与具体使用的字体无关,只要强制把这个属
性设置好了,字体是否支持这个集合由系统自动转换。
因为 WideString 中最需要转换的编码就是 UTF-8,所以演示了 UTF-8 就可以应用到所有
WideString 编码。
下面的演示代码是把 UTF-8 格式的文本装入只能显示 AnsiString 的 Delphi 自带的 Memo
中,并且可以再将这个 Memo 中的 AnsiString 取出来保存为 UTF-8 格式文本,并且支持
在任何语种的 Windows NT 操作系统上显示中文。
unit frmUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ComCtrls, Menus, StdCtrls;
type
TEncodeFlags = (efUnknown, efAnsi, efUnicode, efUncodeBigEn, efUTF8);
TUniEditFrm = class(TForm)
   MainMenu1: TMainMenu;
   mnuFileItem: TMenuItem;
   mnuOpen: TMenuItem;
   mnuSpace1: TMenuItem;
   mnuSaveAs: TMenuItem;
   mnuSpace2: TMenuItem;
   mnuExit: TMenuItem;
   StatusBar: TStatusBar;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
private
   { Private declarations }
   FStream: TStream;
   OpenDlg: TOpenDialog;
   SaveDlg: TSaveDialog;
   UnicoMemo: TMemo;
   procedure SetMemoCharset;
   procedure LoadFromFile(fName: string);
   procedure SaveToFile(fName: string);
   procedure SetStatusMessage(Msg: string);
   procedure MenuItemOnClick(Sender: TObject);
   function ChWideToAnsi(const StrW: WideString): AnsiString;
   function ChAnsiToWide(const StrA: AnsiString): WideString;
   function UTF8ToWideString(const Stream: TStream): WideString;
   procedure TextToUTF8Stream(const Text: string; var Stream: TStream);
   function GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
public
   { Public declarations }
end;
var
UniEditFrm: TUniEditFrm;
implementation
{$R *.dfm}
type
TUTF8Falg = packed record
   EF, BB, BF: Byte;
end;
const
Encode: TUTF8Falg = (EF: $EF; BB: $BB; BF: $BF);
MenuActSpace = 0;
MenuActOpen = 1;
MenuActSaveAs = 2;
MenuActExit = 3;
{ TUniEditFrm }
procedure TUniEditFrm.FormCreate(Sender: TObject);
var
n: integer;
begin
mnuOpen.Tag := MenuActOpen;
mnuSaveAs.Tag := MenuActSaveAs;
mnuExit.Tag := MenuActExit;
for n := 0 to mnuFileItem.Count - 1 do
   if mnuFileItem.Items[n].Caption <> '-' then
     mnuFileItem.Items[n].OnClick := MenuItemOnClick;
OpenDlg := TOpenDialog.Create(Self);
OpenDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg := TSaveDialog.Create(Self);
SaveDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg.DefaultExt := '.txt';
UnicoMemo := TMemo.Create(Self);
UnicoMemo.Parent := Self;
UnicoMemo.Align := alClient;
UnicoMemo.ScrollBars := ssVertical;
SetMemoCharset;
end;
procedure TUniEditFrm.FormDestroy(Sender: TObject);
begin
OpenDlg.Free; SaveDlg.Free; UnicoMemo.Free;
if Assigned(FStream) then FStream.Free;
end;
procedure TUniEditFrm.MenuItemOnClick(Sender: TObject);
begin
case TComponent(Sender).tag of
   MenuActOpen: if OpenDlg.Execute then LoadFromFile(OpenDlg.FileName);
   MenuActSaveAs: if SaveDlg.Execute then SaveToFile(SaveDlg.FileName);
   MenuActExit: Close;
end;
end;
procedure TUniEditFrm.SetMemoCharset;
begin
UnicoMemo.Font.Charset := GB2312_CHARSET;
UnicoMemo.Font.Size := 12;
end;
procedure TUniEditFrm.SetStatusMessage(Msg: string);
begin
SendMessage(StatusBar.Handle, WM_USER + 1, 0, DWord(PChar(Msg)));
end;
procedure TUniEditFrm.LoadFromFile(fName: string);
begin
if not Assigned(FStream) then FStream := TMemoryStream.Create;
TMemoryStream(FStream).LoadFromFile(fName);
if GetEncodeFromStream(FStream) = efUTF8 then
begin
   SetStatusMessage(Format('File: %s ,Size:%d Byte', [fName, FStream.Size]));
   UnicoMemo.Lines.BeginUpdate;
   UnicoMemo.Clear;
   try
     UnicoMemo.Lines.Add(ChWideToAnsi(UTF8ToWideString(FStream)));
   finally
     UnicoMemo.Lines.EndUpdate;
   end;
end
else SetStatusMessage(Format('File: %s ,Unknown Encode', [fName]));
FStream.Size := 0;
end;
procedure TUniEditFrm.SaveToFile(fName: string);
begin
try
   if not Assigned(FStream) then FStream := TMemoryStream.Create;
   TextToUTF8Stream(UnicoMemo.Lines.Text, FStream);
   TMemoryStream(FStream).SaveToFile(fName);
   SetStatusMessage(Format('Save File: %s ,Size:%d Byte', [fName, FStream.Size]));
finally
   FStream.Size := 0;
end;
end;
function TUniEditFrm.ChWideToAnsi(const StrW: WideString): AnsiString;
var
nLen: integer;
begin
Result := StrW;
if Result <> '' then
begin
   nLen := WideCharToMultiByte(936, 624, @StrW[1], -1, nil, 0, nil, nil);
   SetLength(Result, nLen - 1);
   if nLen > 1 then
     WideCharToMultiByte(936, 624, @StrW[1], -1, @Result[1], nLen - 1, nil, nil);
end;
end;
function TUniEditFrm.ChAnsiToWide(const StrA: AnsiString): WideString;
var
nLen: integer;
begin
Result := StrA;
if Result <> '' then
begin
   nLen := MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, nil, 0);
   SetLength(Result, nLen - 1);
   if nLen > 1 then
     MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, PWideChar(@Result[1]), nLen - 1);
end;
end;
function TUniEditFrm.UTF8ToWideString(const Stream: TStream): WideString;
var
nLen: Cardinal;
begin
try
   SetLength(Result, Stream.Size div SizeOf(WideChar) * 3);
   nLen := Utf8ToUnicode(@Result[1], Length(Result),
     Pointer(DWord(TMemoryStream(Stream).Memory) + Stream.Position),
     Stream.Size - Stream.Position);
   SetLength(Result, nLen);
except
   SetLength(Result, 0);
end;
end;
procedure TUniEditFrm.TextToUTF8Stream(const Text: string; var Stream: TStream);
var
StringW, StrW: WideString;
nLen: Cardinal;
begin
try
   if Text <> '' then
   begin
     StrW := ChAnsiToWide(Text);
     nLen := Length(StrW) * 3;
     SetLength(StringW, nLen);
     nLen := UnicodeToUtf8(@StringW[1], nLen, @StrW[1], Length(StrW));
     SetLength(StringW, nLen);
     Stream.Write(Encode, SizeOf(Encode));
     Stream.Write(StringW[1], Length(StringW));
   end
   else
     Stream.Write(Encode, SizeOf(Encode));
except
   SetLength(StrW, 0);
   SetLength(StringW, 0);
end;
end;
function TUniEditFrm.GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
var
FEncode: TUTF8Falg;
begin
Result := efUnknown;
Stream.Read(FEncode, SizeOf(FEncode));
if (FEncode.EF = Encode.EF) and (FEncode.BB = Encode.BB)
   and (FEncode.BF = Encode.BF) then Result := efUTF8;
end;
end.
代码中有几个控件是动态创建的,其中有对话框和 Memo。这是为了随时改变使用不同控件
库进行测试观察的需要,如果你使用支持宽字符的控件,不用改界面,直接把创建实例改一
改就可以测试观察了。这种写法不是最好的,如果使用接口来描述就会更随意些。
-------------------------------------------------------------------------------------
干净搞定delphi多语言-兼论设计模式    
       随着全球化程度加深,软件越来越像蒲公英,到处飘散、扎根。这其中要解决的是不同语言的显示问题。我们当然希望一套程序,可以不修改代码就可以支持不同的语言,不要去维护很多的版本。
       首先要谈到的一个问题是乱码问题,因为delphi win32到11.x版还是不支持unicode,所以一般使用Ansi码,有这样几种情况会显示乱码:

  1. 使用的语言文字与系统当前设定的语言不一样;比如简体版QQ在繁体操作系统(或简体操作系统的区域设置中“非Unicode程序的语言”设定为 繁体)就是乱码。即使改变Font.Charset,某些元件仍然会出现乱码,如StatusBar。因此,在越南文版的windows显示越南文,在伊 朗文版的windows显示伊朗文,不要在越南文版windows显示伊朗文,在伊朗文版windows显示越南文,这样就能确保没有乱码问题。好在一般 这样的错位用法也不多见。
  2. 系统没有安装你要显示的语言的语言包;

       如果你要保证完全无乱码,必须考虑使用unicode码,使用成套的支持unicode的元件,如tnt,但它在UI变现上比较单一,你不可能不使用别的元件。
       言归正传,首先,看看哪些地方的字串需要实现多语言,并来看看各种实现方法的优劣。
           1、界面上的元件,如TButton的Caption;
           2、主动弹出的消息,如ShowMessage('Are you sure?'),Raise Exception.Create('Error!');
           3、例外错误举发的报告信息,如f/0引起的exception;
           4、第3方元件包内部的上述字串;
       实现多语言的方法很多,列举一二:
           1、delphi自带的Resource生成工具
               此工具把专案的dfm文件里的所有字串以及pas中定义为ResourceString的字串列举出来,按不同的语言编译成不同的Resource,专案编译前先选语言,每种语言编译成一个exe。
                这个工具使用很不方便,不是一个完整的解决方案,跟Borland的Midas的demo一样(TClientDataSet通过 ProviderName连接到RemoteDatamodule的TDataSetProvider,实际开发Erp系统时,谁会放100个 TDataSetProvider连接到100个TDataSet?),只是一个原理尚通的示范。
               首先,由于dfm本身也是资源文件的一部分,因此每次修改都要“Update Resource DLLs...”,如修改Button1为Button2,如果你忘了,运行时就会报“找不到资源Button1的错误”;提供的字典编辑画面中,出了字 串,还有Left/Top等资料;字典不能重用,在一个模组翻译了,在第2个模组还要再翻译相同的词。
              其次,每种语言一个exe/bpl,如果你的系统是Package切割,bpl也是每种语言一个,还要小心别把不同语言的bpl组合在了一起,到时候一个画面显示中文,一个显示德文(有一个可能是乱码)就惨了。
              再次,在作bpl组装的系统时,第3方元件如果没有提供多语言的方案,你就需要修改第3方元件,但一般我们不这样干,因为第3方元件会随时更新,难道每次人家更新你也再更新人家。
              因此,一般都没有人使用Delphi本身提供的这个方案(除了作demo)。
           2、Resource dll方式
              用单独的ResourceDll,用LoadResString等函数获得翻译字串,但你要到处写这个函数来一一替换,特别是Form上的字串,噢,会累死人。字典可以重用。
           3、网上讨论很多的ini文件方式
               此方法是写个替换的引擎,在运行时从ini文件读取语言字串来替换画面元件的显示文字。这个方法比第一种进步很多,不需要每种语言编译一个exe了,只要 提供不同的ini文件就好;画面修改时如果ini没有同步更新也不会出现致命错误,最多就是某个文字没有转换;引擎也提供了字串转换函数,因此也可以处理 主动弹出的消息。这个方法在文件格式上有三种不同的实现:
               (1)、[编号]=[字串]
                     每个字串从1开始编号,1,2,3,4......,很麻烦,代码要修改,当然运行时切换语言没问题。
               (2)、[元件.属性]=[字串]
                     这种实现把元件instance一一对应,用RTTI来判断属性,替换很精确,也可以运行时切换语言。不足之处是,略显呆板,多个元件相同的字串会多次列 出;没有扩展性,表现TListView的Columns等复杂元件时比较吃力。
               (3)、[旧字串]=[新字串]
                     不管元件的instance,ini是纯粹的语言对照表,或者叫字典,扩展性、运行时切换语言可能在引擎里。不足之处是不能处理一词多义。
               总的说来,这种方式有很大进步,但为了用ini文件,大家还要费力的破解64k的限制,更专业的方式是使用自定义的文件格式。
               在简单性方面,无疑是这种自定义的转换引擎,[旧字串]=[新字串]的文件格式来得方便,借助字典管理工具,字典文件可以重复使用,也可以提供给专业翻译 公司翻译。那么剩下的问题在引擎上,如何方便,最好用户不写一行代码;如何扩展性强,支持任意的第三方元件;如何有弹性,同一个画面有多种语言的文字,同 一个词可以转换成不同的意思......
            4、给每个元件类继承一个子类,在子类的Loaded方法里转换文字。由于要处理的都是叶级元件(虽然TLabel、TPanel都是从 TCustomControl来,但不能只处理TCustomControl),工作量比较大;对旧有程序除了换元件无能为力。
           5、为每个元件类注册一个转换函数,引擎遍历Container,为每个元件找到血源最近的转换函数,调用这个函数转换这个元件的文字。这样可以不必处理 叶结点,只需在恰当的元件层上注册函数;不必改动旧有程序。設計時Form上只需要放一個轉換元件,這個元件在Loaded后開始掃描Form上的元件, 從for I:=0 to ComponentCount-1或從for i:=0 to ControlCount-1遞歸,找到一個元件就去查找其血緣最近的註冊函數,然後調用這個函數替換其文字。因爲註冊函數是額外加上去的,所以不會動到 舊的代碼,對任意第方元件都可以擴展支持,且也不用去修改人家第3方元件的代碼。
我认为第5种方法很优雅,看起来比较干净。用GOF的设计模式来套,这属 于Mediator pattern(中介者模式)。多年前,我们使用一个叫TXPMenu的元件来获得XP风格的界面,也是感觉到它很干净,一个元件就搞定一切,不用 TLabel换成TFlatLabel,TButton换成TFlatButton......我记得《程序员》上还有文章专门称赞这个元件。但那个元件 没有使用中介者模式,不能很好的扩展对第3方元件的支持。
        最后,我们畅想一下,如果我是Borland,如何在Delphi里完整支援多语言。Delphi提供了一个区块定义的关键字 “ResourceString”,在这个区块定义的字串常量,编译器会把它编译在exe文件的资源区,运行时用LoadStringA这个 Windows API来读取,因此有些外部转换工具可以直接从exe文件读取这些资源字串,再写入转换后的字串;内嵌的转换引擎也可以拦截这个API函数来转换文字。但 是如果exe里的字串资源化不彻底,就无能为力,这个不彻底恰恰来自Delphi的DFM文件,Delphi把DFM文件整个作为一项资源放在exe里, 其上的字串就没法决定是否要don`t resource(Delphi源码里很多常量字串都有这个提示)了。
        如果除了string,widestring,ansistring等等这些数据类型,delphi增加一种数据类型multistring,然后修改 vcl元件定义(拜托Borland连同Unicode一起解决了吧),像TLabel.Caption定义成MultiString,对 MultiString类型,有一种专门的处理方法,如类似ResourceString用LoadString API来处理,每次读取就转换一次,但应该比这个内容更多,比如要传出instance,然后提供一个全局的ApplicationMulti元件,类似 ApplicationEvent,让外面能捕捉到。至于字典,只能外部用户提供(当然可以制定一个标准格式让delphi人都可以共享交换)。
      此法看起来可行,但还有个效率问题要考虑,(1)每次读取都转换,对频繁 draw的东西效率低;(2)比如一个ToolBar有好多的ToolButton,批次更新时一般都会用 BeginUpdate/EndUpdate,vcl如何告知后代来提高这种效率。(补记:效率看起来不是问题,对多次字串更改导致频繁draw,其实元 件自己已经会用beginupdate/endupdate处理,外部不会涉及)


Total views.

© 2013 - 2024. All rights reserved.

Powered by Hydejack v6.6.1