http://www.delphibbs.com/delphibbs/dispq.asp?lid=3349991
问题: WideString 还是 AnsiString ?谈谈字符编码。 ( 积分:0, 回复:12, 阅读:242 )
分类: 编程手记 ( 版主:DNChen, cAkk )
来自: 小雨哥 , 时间: 2006-2-10 17:12:00, ID: 3349991
这篇这次不给分了,我发现我的分开始只降不升了,长此以往,岂不穷死。
本 来这一篇是讲关于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的中文字符集。这个设置与具体使用的字体无关,只要强制把这个属
性设置好 了,字体是否支持这个集合由系统自动转换。
如果要了解更多这方面的情况,建议使用虚拟电脑模拟语言环境来观察。暂时就先到这里。
本 来这一篇是讲关于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的中文字符集。这个设置与具体使用的字体无关,只要强制把这个属
性设置好 了,字体是否支持这个集合由系统自动转换。
如果要了解更多这方面的情况,建议使用虚拟电脑模拟语言环境来观察。暂时就先到这里。
来 自:
satanmonkey ,
时间: 2006-2-10 20:09:45,
ID: 3350123
好文章,收藏
来 自:
小雨哥 ,
时间: 2006-2-11 11:24:21,
ID: 3350814
呵呵,没人拍砖,象是感兴趣 的人不多啊。那就收尾吧。
因为 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的接口在编程中的应用吧。这一篇就到这里 了。
因为 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的接口在编程中的应用吧。这一篇就到这里 了。
来 自:
dirk ,
时间: 2006-2-11 11:38:39,
ID: 3350833
懒啊,你每天登录一把不就有 分了?[:D]
好文,顶!
好文,顶!
来 自:
anyway ,
时间: 2006-2-11 21:01:15,
ID: 3351261
哎呀!咋不早说!你发贴的前 一天我就让XML的字符编码差点没搞崩溃了
幸亏后来解决了
不过还是收藏了,谢谢楼主
幸亏后来解决了
不过还是收藏了,谢谢楼主
来 自:
anyway ,
时间: 2006-2-11 21:07:10,
ID: 3351264
小雨哥
请教一下:
可 不可以用文件流直接读入文本文件
然后用上述函数转换成ansiString?
请教一下:
可 不可以用文件流直接读入文本文件
然后用上述函数转换成ansiString?
来 自:
小雨哥 ,
时间: 2006-2-12 10:31:27,
ID: 3351314
你这里指的“文件流”是什么 概念?磁盘文件?还是某块内存?还是 Delphi 的 TStream?
我想应该不是指 TStream 。那么好,假设它是磁盘文件好了。当使用 FileOpen 后,你可
以直接用 CreateFileMapping 把这个文件建成映像,如果这是一个已经存在的物理文件,
那么 GetFileSize 可以知道这个文件的真实大小。CreateFileMapping 后就可以使用文件
映像的一系列操作获得这个文件了。
使用 MapViewOfFile 返回的指针就是文件的最开头。当你已经能够确切地知道这个文件是
某个类型、某种编码的文件,并且很肯定的话,那么我 下面说的步骤你可以酌情省略。
读取文件头,检查是否是你要处理的文件,比如 Unicode ,你可以定义一个 Word 变量:
var
Unicode:Word;
begin
Move(MapViewOfFile^,@Unicode,SizeOf(Word));
end;
这 样就取到了 2 个字节的 Unicode 编码标志,检查它是否符合 Unicode 标志,不是就放
弃,是的话就继续。
获知 它确实是你要的 Unicode 文件后,继续以前,你要确定 1 件事,就是你是否打算分段
读取。文件映像你不读它,它是不会占用你的内存的, 如果你只需要一点一点地显示,那么
可以采用分段读取。这里假设你采用分段读取,只读其中的一部分:
function UnicodeFileStreamToAnsiString(ptr:Pointer;nLen:DWord):AnsiString;
var
StrW:WideString;
begin
SetLength(StrW,nLen div SizeOf(WideChar));
Move(ptr^,@StrW,Length(StrW)*SizeOf(WideChar));
Result:=StrW;
end;
好 了。现在你已经读到了需要的长度的 Unicode 文本了,值得注意的是:这个 ptr 是减掉
Unicode 文件头后的任意 Word 对齐的位置,并且这个函数正好利用了 Delphi 自动转换
WideString 到 AnsiString 的特性。这时输出的结果已经是你需要的 AnsiString 了。
假如只是一个小文件,你可能采用一次性全部读取,在 Unicode 一次读取中,你最好在建
映像文件的时候就考虑到要这样做,这样的话,你可以提前在建文件映像的时候把文件映像
建得比实际尺寸大一个 Word 字节,并设置可写属性,当确定是一个想要的 Unicode 文件
后,你只要在增加的那个 Word 字节里填入两个 #0,就可以把这个文件映像直接当成内存
中的 PWideChar 来使用,也就意味着连转换代码都不用写,全部由 Delphi 帮你解决。
如 果你正好很不巧地要读的是 UTF-8 文本,那么你不能分割了,你只能使用我上面代码中
提供的示范,不过为了读取磁盘文件流,代码将改成下面的 样子:
function UTF8ToWideString(ptr:Pointer;FileSize:DWord): WideString;
var
nLen: Cardinal;
begin
SetLength(Result, FileSize div SizeOf(WideChar) * 3);
nLen := Utf8ToUnicode(@Result[1], Length(Result),
Pointer(DWord(ptr) + SizeOf(TUTF8Falg)),
FileSize - SizeOf(TUTF8Falg));
SetLength(Result, nLen);
end;
注意:这个函数里的 ptr 是从文件的最开头算起。
这 个函数返回一个 WideString ,你爱转换也好,你不爱转换也好,我上面帖子已经说了,
Delphi 自动会把 WideString 转换为 AnsiString ,你只要这样:
var
s:AnsiString
begin
s:=UTF8ToWideString(...);
end;
如 果希望 UTF-8 也要分段读取,就要自己完成自己的解码算法。我上面帖子里提到的几个
著名的开放源项目中都有代码例子可供参考。
好 了,就这些,其实比 TStream 还简单。这里介绍的是文件映像,其实直接使用 ReadFile
(注意不是 Delphi 中的文件操作,是指 API 的文件操作)操作也一样。
我想应该不是指 TStream 。那么好,假设它是磁盘文件好了。当使用 FileOpen 后,你可
以直接用 CreateFileMapping 把这个文件建成映像,如果这是一个已经存在的物理文件,
那么 GetFileSize 可以知道这个文件的真实大小。CreateFileMapping 后就可以使用文件
映像的一系列操作获得这个文件了。
使用 MapViewOfFile 返回的指针就是文件的最开头。当你已经能够确切地知道这个文件是
某个类型、某种编码的文件,并且很肯定的话,那么我 下面说的步骤你可以酌情省略。
读取文件头,检查是否是你要处理的文件,比如 Unicode ,你可以定义一个 Word 变量:
var
Unicode:Word;
begin
Move(MapViewOfFile^,@Unicode,SizeOf(Word));
end;
这 样就取到了 2 个字节的 Unicode 编码标志,检查它是否符合 Unicode 标志,不是就放
弃,是的话就继续。
获知 它确实是你要的 Unicode 文件后,继续以前,你要确定 1 件事,就是你是否打算分段
读取。文件映像你不读它,它是不会占用你的内存的, 如果你只需要一点一点地显示,那么
可以采用分段读取。这里假设你采用分段读取,只读其中的一部分:
function UnicodeFileStreamToAnsiString(ptr:Pointer;nLen:DWord):AnsiString;
var
StrW:WideString;
begin
SetLength(StrW,nLen div SizeOf(WideChar));
Move(ptr^,@StrW,Length(StrW)*SizeOf(WideChar));
Result:=StrW;
end;
好 了。现在你已经读到了需要的长度的 Unicode 文本了,值得注意的是:这个 ptr 是减掉
Unicode 文件头后的任意 Word 对齐的位置,并且这个函数正好利用了 Delphi 自动转换
WideString 到 AnsiString 的特性。这时输出的结果已经是你需要的 AnsiString 了。
假如只是一个小文件,你可能采用一次性全部读取,在 Unicode 一次读取中,你最好在建
映像文件的时候就考虑到要这样做,这样的话,你可以提前在建文件映像的时候把文件映像
建得比实际尺寸大一个 Word 字节,并设置可写属性,当确定是一个想要的 Unicode 文件
后,你只要在增加的那个 Word 字节里填入两个 #0,就可以把这个文件映像直接当成内存
中的 PWideChar 来使用,也就意味着连转换代码都不用写,全部由 Delphi 帮你解决。
如 果你正好很不巧地要读的是 UTF-8 文本,那么你不能分割了,你只能使用我上面代码中
提供的示范,不过为了读取磁盘文件流,代码将改成下面的 样子:
function UTF8ToWideString(ptr:Pointer;FileSize:DWord): WideString;
var
nLen: Cardinal;
begin
SetLength(Result, FileSize div SizeOf(WideChar) * 3);
nLen := Utf8ToUnicode(@Result[1], Length(Result),
Pointer(DWord(ptr) + SizeOf(TUTF8Falg)),
FileSize - SizeOf(TUTF8Falg));
SetLength(Result, nLen);
end;
注意:这个函数里的 ptr 是从文件的最开头算起。
这 个函数返回一个 WideString ,你爱转换也好,你不爱转换也好,我上面帖子已经说了,
Delphi 自动会把 WideString 转换为 AnsiString ,你只要这样:
var
s:AnsiString
begin
s:=UTF8ToWideString(...);
end;
如 果希望 UTF-8 也要分段读取,就要自己完成自己的解码算法。我上面帖子里提到的几个
著名的开放源项目中都有代码例子可供参考。
好 了,就这些,其实比 TStream 还简单。这里介绍的是文件映像,其实直接使用 ReadFile
(注意不是 Delphi 中的文件操作,是指 API 的文件操作)操作也一样。
来 自:
小雨哥 ,
时间: 2006-2-12 0:21:54,
ID: 3351315
to dirk
你 好像也是超级潜水员啊,论坛潜水还不够,QQ 也潜水,不过弄不懂你潜水怎么还涨分,我就不涨呢。
你 好像也是超级潜水员啊,论坛潜水还不够,QQ 也潜水,不过弄不懂你潜水怎么还涨分,我就不涨呢。
来 自:
anyway ,
时间: 2006-2-12 13:57:03,
ID: 3351427
to dirk
那么 直接用FileStream打开的TXT文件,前两个字节是数据还是编码类型?
那么 直接用FileStream打开的TXT文件,前两个字节是数据还是编码类型?
来 自:
小雨哥 ,
时间: 2006-2-12 16:33:07,
ID: 3351434
anyway,你可能是问我 吧?
如果你说的“FileStream”,是Delphi 中的 TFileStream 的话,那么直接可以用我上面例
子代码 中的片段,如果你是指文件映像这样的“流”的话,那么应该知道文件映象创建起来
后,需要通过MapViewOfFile才能得到读取的具体内存位 置。这个函数声明:
function MapViewOfFile(
hFileMappingObject: THandle; // file-mapping object to map into address space
dwDesiredAccess: DWORD; // access mode
dwFileOffsetHigh, // high-order 32 bits of file offset
dwFileOffsetLow, // low-order 32 bits of file offset
dwNumberOfBytesToMap: // number of bytes to map
DWORD): Pointer; stdcall;
其中 3、4 的参数设置指针位置,如果你设置为 0,0 ,就是文件的最开头。其中应该就包
括编码类型指示的那 2 个字节。ReadFile 也是这样,可以看一下函数声明。ReadFile 函
数你还可以自定义一个类型给它读,比如:
TImNeedRead = packed record
Falg:Word;
Text:array[0..0]of WideChar;
end;
这样就直接可以通 过一个简单的结构保证读取指针的正常。是不是很有趣。呵呵。
如果你说的“FileStream”,是Delphi 中的 TFileStream 的话,那么直接可以用我上面例
子代码 中的片段,如果你是指文件映像这样的“流”的话,那么应该知道文件映象创建起来
后,需要通过MapViewOfFile才能得到读取的具体内存位 置。这个函数声明:
function MapViewOfFile(
hFileMappingObject: THandle; // file-mapping object to map into address space
dwDesiredAccess: DWORD; // access mode
dwFileOffsetHigh, // high-order 32 bits of file offset
dwFileOffsetLow, // low-order 32 bits of file offset
dwNumberOfBytesToMap: // number of bytes to map
DWORD): Pointer; stdcall;
其中 3、4 的参数设置指针位置,如果你设置为 0,0 ,就是文件的最开头。其中应该就包
括编码类型指示的那 2 个字节。ReadFile 也是这样,可以看一下函数声明。ReadFile 函
数你还可以自定义一个类型给它读,比如:
TImNeedRead = packed record
Falg:Word;
Text:array[0..0]of WideChar;
end;
这样就直接可以通 过一个简单的结构保证读取指针的正常。是不是很有趣。呵呵。
来 自:
andyzhouap98111 ,
时间: 2006-2-12 14:19:47,
ID: 3351441
在delphi里如 Label的Caption中的汉字在自动长度状态下会看不到后面部分是不是和这个有关
来 自:
anyway ,
时间: 2006-2-12 15:19:07,
ID: 3351453
to 小雨哥
好,我 试试,谢谢
好,我 试试,谢谢
来 自:
小雨哥 ,
时间: 2006-2-17 23:12:24,
ID: 3356675
To andyzhouap98111:
你说的情况和 AnsiString、WideString 没有关系。那是字符集的关系。Label 中计算文字
需要多少宽度由系统来进行,系统则根据 HDC 的字符集属性来计算正确的尺度。你只要为
Label 在显示中文的时候,正确地选择 GB2312 字符集就可以了。
你说的情况和 AnsiString、WideString 没有关系。那是字符集的关系。Label 中计算文字
需要多少宽度由系统来进行,系统则根据 HDC 的字符集属性来计算正确的尺度。你只要为
Label 在显示中文的时候,正确地选择 GB2312 字符集就可以了。