目标多字节代码页中不存在Unicode字符的映射。

19

我有一个错误报告,显示出一个 EEncodingError。日志指向 TFile.AppendAllText。我在我的这个过程中调用了 TFile.AppendAllText

procedure WriteToFile(CONST FileName: string; CONST uString: string; CONST WriteOp: WriteOpperation; ForceFolder: Boolean= FALSE);     // Works with UNC paths
begin
 if NOT ForceFolder
 OR (ForceFolder AND ForceDirectoriesMsg(ExtractFilePath(FileName))) then
   if WriteOp= (woOverwrite)
   then IOUtils.TFile.WriteAllText (FileName, uString)
   else IOUtils.TFile.AppendAllText(FileName, uString);
end;

这是来自于 EurekaLog 的信息。

enter image description here

enter image description here

这可能是什么原因呢?


2
类似的问题出现在Delphi 10.2中。 - Zam
1
是啊...几年过去了,这个漏洞还在! - Gabriel
相关的 https://stackoverflow.com/questions/26060832/delphi-xe6-no-mapping-for-the-unicode-character-exists-in-the-target-multi-byte - Gabriel
错误报告:https://quality.embarcadero.com/browse/RSP-41439 - Gabriel
2个回答

24

这个程序复现了你报告的错误:

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.IOUtils;

var
  FileName: string;

begin
  try
    FileName := TPath.GetTempFileName;
    TFile.WriteAllText(FileName, 'é', TEncoding.ANSI);
    TFile.AppendAllText(FileName, 'é');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

我原先将文件编码为ANSI格式,然后调用了AppendAllText函数来尝试使用UTF-8格式进行写入。结果导致我们进入了这个函数:

class procedure TFile.AppendAllText(const Path, Contents: string);
var
  LFileStream: TFileStream;
  LFileEncoding: TEncoding; // encoding of the file
  Buff: TBytes;
  Preamble: TBytes;
  UTFStr: TBytes;
  UTF8Str: TBytes;
begin
  CheckAppendAllTextParameters(Path, nil, False);

  LFileStream := nil;
  try
    try
      LFileStream := DoCreateOpenFile(Path);
      // detect the file encoding
      LFileEncoding := GetEncoding(LFileStream);

      // file is written is ASCII (default ANSI code page)
      if LFileEncoding = TEncoding.ANSI then
      begin
        // Contents can be represented as ASCII;
        // append the contents in ASCII

        UTFStr := TEncoding.ANSI.GetBytes(Contents);
        UTF8Str := TEncoding.UTF8.GetBytes(Contents);

        if TEncoding.UTF8.GetString(UTFStr) = TEncoding.UTF8.GetString(UTF8Str) then
        begin
          LFileStream.Seek(0, TSeekOrigin.soEnd);
          Buff := TEncoding.ANSI.GetBytes(Contents);
        end
        // Contents can be represented only in UTF-8;
        // convert file and Contents encodings to UTF-8
        else
        begin
          // convert file contents to UTF-8
          LFileStream.Seek(0, TSeekOrigin.soBeginning);
          SetLength(Buff, LFileStream.Size);
          LFileStream.ReadBuffer(Buff, Length(Buff));
          Buff := TEncoding.Convert(LFileEncoding, TEncoding.UTF8, Buff);

          // prepare the stream to rewrite the converted file contents
          LFileStream.Size := Length(Buff);
          LFileStream.Seek(0, TSeekOrigin.soBeginning);
          Preamble := TEncoding.UTF8.GetPreamble;
          LFileStream.WriteBuffer(Preamble, Length(Preamble));
          LFileStream.WriteBuffer(Buff, Length(Buff));

          // convert Contents in UTF-8
          Buff := TEncoding.UTF8.GetBytes(Contents);
        end;
      end
      // file is written either in UTF-8 or Unicode (BE or LE);
      // append Contents encoded in UTF-8 to the file
      else
      begin
        LFileStream.Seek(0, TSeekOrigin.soEnd);
        Buff := TEncoding.UTF8.GetBytes(Contents);
      end;

      // write Contents to the stream
      LFileStream.WriteBuffer(Buff, Length(Buff));
    except
      on E: EFileStreamError do
        raise EInOutError.Create(E.Message);
    end;
  finally
    LFileStream.Free;
  end;
end;

这个错误源自于这一行:

if TEncoding.UTF8.GetString(UTFStr) = TEncoding.UTF8.GetString(UTF8Str) then

问题在于UTFStr实际上不是有效的UTF-8编码,因此TEncoding.UTF8.GetString(UTFStr)会抛出异常。

TFile.AppendAllBytes存在这个缺陷。鉴于它非常清楚UTFStrANSI编码,它调用TEncoding.UTF8.GetString毫无意义。

您应该向Embarcadero提交一个缺陷报告,说明这个问题仍然存在于Delphi 10 Seattle中。同时,您不应该使用TFile.AppendAllBytes


TStreamReader怎么样?它似乎是一个不错的选择,而且它不基于IOUtils。 - Gabriel
性能有点不稳定。我不想在不了解文件的生命周期和其他谁修改它的情况下提供建议。 - David Heffernan
6
这个缺陷在 Delphi 10.4 中仍然存在,并且会影响其他函数,例如用于解码 Base64 的 DecodeStream。 - Somebody
再试一次:https://quality.embarcadero.com/browse/RSP-41533 - Gabriel
为什么你把我的完整程序(一个控制台应用程序)变成了无法直接运行的部分程序?你知道 [mcve] 吗?不管怎样,我打算把这个程序提交作为报告...... - David Heffernan
显示剩余2条评论

0

这样做就可以了:

TFile.WriteAllText(FileName, 'é', TEncoding.UTF8);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接