WideString 还是 AnsiString ?谈谈字符编码

本文探讨了Delphi7中字符编码处理的方法,包括AnsiString与WideString的转换、不同编码格式的读写,以及如何通过内置函数实现UTF-8与其他编码间的转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

From:http://blog.youkuaiyun.com/xwchen/archive/2007/03/21/1536829.aspx 

作者:佚名    文章来源:不详    点击数:    更新时间:2007-8-1   

      这篇这次不给分了,我发现我的分开始只降不升了,长此以往,岂不穷死。
            正在装载数据……


      本来这一篇是讲关于XML字符编码的,我觉得写着写着好像与XML的关系不大了,就改了标题
      。所以,看的时候如果感觉到摸不到头脑,那就对了,如果感觉和你的认识不一样,欢迎批
      评指正。

      这里还有一个字符编码的问题。字符编码在Delphi7中已经得到了很大提高。
      Delphi7自己的IDE虽然不能读取Unicode编码的源代码文件,但编译器已经支持
      AnsiString和WideString的转换。也就是说,只要定义的时候定义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的接口在编程中的应用吧。这一篇就到这里了。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值