XE2和Delphi 2009之间的unicode文本文件输出不同?

XE2和Delphi 2009之间的unicode文本文件输出不同?,delphi,unicode,utf-8,Delphi,Unicode,Utf 8,当我尝试下面的代码时,XE2中的输出似乎与D2009中的不同 procedure TForm1.Button1Click(Sender: TObject); var Outfile:textfile; myByte: Byte; begin assignfile(Outfile,'test_chinese.txt'); Rewrite(Outfile); for myByte in TEncoding.UTF8.GetPreamble do write(Outfile,

当我尝试下面的代码时,XE2中的输出似乎与D2009中的不同

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    myByte: Byte;

begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Writeln(Outfile,utf8string('总结'));
  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;
在Windows 8 PC上使用XE2编译会在写字板中提供

??? C

txt十六进制代码:EF BB BF 3F 3F 0D 0A B0 43 0D 0A

在Windows XP PC上使用D2009进行编译会在写字板中显示

总结 摄氏度

txt十六进制代码:EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A

我的问题是为什么它不同,以及如何使用旧的文本文件I/O将汉字保存到文本文件中


谢谢

您真的不应该再使用旧的文本I/O了

无论如何,您可以使用十位编码获得UTF-8 TB,如下所示:

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    Bytes: TBytes;
    myByte: Byte;
begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Bytes := TEncoding.UTF8.GetBytes('总结');
  for myByte in Bytes do begin
    Write(Outfile, AnsiChar(myByte));
  end;

  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;
function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
  N := Length(InStr);
  SetLength(Result, N);
  Move(InStr[1], Result[1], N);
end;
我不确定是否有更简单的方法将TBytes写入文本文件,也许其他人有更好的主意

编辑:


对于纯二进制文件(
file
而不是
TextFile
类型),use可以使用
BlockWrite

有几个指示符号,可以告诉您在处理Unicode时什么地方出了问题。在您的例子中,您在生成的输出文件中看到“
”:当您尝试将某些内容从Unicode转换为代码页时,会出现问号,而目标代码页无法表示请求的字符

查看十六进制转储,很明显(计算行终止符)问号是将两个汉字保存到文件中的结果。这两个字符正好变成了两个问号。这说明
Writeln()
决定为您提供帮助,并将文本从UTF8(unicode表示)转换为本地代码页。Delphi团队可能决定这样做,因为旧的I/O例程不应该与UNICODE兼容;因为您正在使用旧的I/O例程编写UTF8字符串,所以它们通过将其转换为代码页来帮助您。你可能不欢迎这一援助之手,但这并不意味着这样做是错误的:这是一个没有记录的领域

既然你现在知道为什么会这样,你就知道该怎么阻止它了。让
WriteLn()
知道您正在发送不需要转换的内容。您会发现这并不特别容易,因为Delphi XE2显然“帮助您解决”了您需要的任何问题。例如,像这样的东西不仅会更改字符串类型,还会转换为AnsiString,通过代码页转换例程获得问号:

AnsiString(UTF8String('Whatever Unicode'));
因此,如果您需要单行程序解决方案,您可以尝试转换例程,如下所示:

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    Bytes: TBytes;
    myByte: Byte;
begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Bytes := TEncoding.UTF8.GetBytes('总结');
  for myByte in Bytes do begin
    Write(Outfile, AnsiChar(myByte));
  end;

  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;
function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
  N := Length(InStr);
  SetLength(Result, N);
  Move(InStr[1], Result[1], N);
end;
然后,您将能够执行以下操作:

Writeln(Outfile,FakeConvert('总结'));
它会做你期望的事情(我在发布之前确实试过!)

当然,这个问题的唯一正确答案是,由于您一直升级到Delphi XE2:

停止使用不推荐使用的I/O例程,在XE2以后的版本中,移动到基于TStream的,具有一个可选的
CodePage
参数,用于设置输出文件的代码页:

function AssignFile(var F: File; FileName: String; [CodePage: Word]): Integer; overload;
Write()
Writeln()
都有支持
UnicodeString
WideChar
输入的重载

因此,您可以创建一个文件,将其代码页设置为
CP\u UTF8
,然后
Write/ln()
将Unicode字符串写入文件时自动转换为UTF-8

缺点是,您将无法再使用
AnsiChar
值写入UTF-8 BOM,因为单个字节将转换为UTF-8,因此无法正确写入。您可以通过将BOM作为单个Unicode字符(它实际上是什么-
U+FEFF
)而不是单个字节来编写来解决这个问题

这在XE2中起作用:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TextFile;
begin
  AssignFile(Outfile, 'test_chinese.txt', CP_UTF8);
  Rewrite(Outfile);

  //This is the UTF-8 BOM
  Write(Outfile, #$FEFF);

  Writeln(Outfile, '总结');
  Writeln(Outfile, '°C');
  CloseFile(Outfile);
end;
话虽如此,如果您希望D2009和XE2之间更兼容、更可靠,请使用
TStreamWriter

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TStreamWriter;
begin
  Outfile := TStreamWriter.Create('test_chinese.txt', False, TEncoding.UTF8);
  try
    Outfile.WriteLine('总结');
    Outfile.WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;
或者手动执行文件I/O:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TFileStream;
  BOM: TBytes;

  procedure WriteBytes(const B: TBytes);
  begin
    if B <> '' then Outfile.WriteBuffer(B[0], Length(B));
  end;

  procedure WriteStr(const S: UTF8String);
  begin
    if S <> '' then Outfile.WriteBuffer(S[1], Length(S));
  end;

  procedure WriteLine(const S: UTF8String);
  begin
    WriteStr(S);
    WriteStr(sLineBreak);
  end;

begin
  Outfile := TFileStream.Create('test_chinese.txt', fmCreate);
  try
    WriteBytes(TEncoding.UTF8.GetPreamble);
    WriteLine('总结');
    WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;
procedure TForm1.按钮1点击(发送方:TObject);
变量
输出文件:TFileStream;
BOM:TBytes;
过程写入字节(常量B:T字节);
开始
如果是B'',则为Outfile.WriteBuffer(B[0],长度(B));
结束;
过程WriteStr(常量S:UTF8String);
开始
如果是S“”,则为Outfile.WriteBuffer(S[1],长度);
结束;
过程写入线(常数S:UTF8String);
开始
书面记录;
书面记录(sLineBreak);
结束;
开始
Outfile:=TFileStream.Create('test_chinese.txt',fmCreate);
尝试
写字节(TEncoding.UTF8.GetPreamble);
WriteLine('总结');
写线(°C');
最后
输出文件。自由;
结束;
结束;

旧文本文件IO官方不支持unicode。不要依赖它。如果这样做,请注意实现有缺陷,并且缺陷因Delphi版本而异。这不是TStreamWriter的工作吗?
TStreamWriter
?实际上,至少在XE2中,旧样式文件I/O确实对unicode有一些支持。
AssigFile()
具有可选的
CodePage
参数,
Write/ln()
具有接受
UnicodeString
WideChar
输入.TFile.writealBytes的重载(常量路径:字符串;常量字节:TBytes)from System.IOUtils可以将t字节写入一个文件。@Giel不会写入BOM。如果您想将内容以零碎的形式写入,这并不方便。谢谢Jens。我使用旧文本I/O的原因是D2009项目有很多行代码,我只想使用XE2快速而肮脏的解决方案。感谢您Cosmin的帮助这个解决方案和解释也是如此!有一个更简单的解决方案。在XE2中,至少,
TextFile
Writeln()
实际上支持Unicode。请参阅我的答案以获取示例。Delphi XE2(包括最新的XE5)当旧的好writeln被破坏时,它犯了一个很大的错误。writeln非常有用而且速度很快,我的测试用例显示TStreamWriter非常慢。当你编写控制台甚至cgi应用程序时,使用TStreamWriter是不可能的。