有一种简单的方法来解决Delphi utf8文件缺陷吗?

时间:2013-05-13 23:11:13

标签: delphi utf-8

我发现(困难的方法)如果一个文件有一个有效的UTF-8 BOM但包含任何无效的UTF8编码,并且被任何Delphi(2009+)编码启用的方法读取,例如{{1然后结果是一个完全空的文件,没有错误指示。在我的几个应用程序中,我宁愿简单地丢失一些不良编码,即使在这种情况下我也没有得到任何错误报告。

调试显示LoadFromFile被调用两次,首先获取输出缓冲区大小,然后进行转换。但是TEncoding.UTF8包含这些调用的私有MultiByteToWideChar值,并使用FMBToWCharFlags值进行初始化。因此,获取charcount的调用返回0并且加载的文件完全为空。在没有标志的情况下调用此API将“无声地删除非法代码点”。

我的问题是如何最好地编织Encoding区域中的类的嵌套来解决这个私有值的事实(并且需要,因为它是所有线程的类var)。我想我可以使用Marco Cantu的Delphi 2009书中的指南添加自定义UTF8编码。如果MB_ERR_INVALID_CHARS返回编码错误,在没有标志的情况下再次调用它之后,它可以选择性地引发异常。但这并没有解决如何使用自定义编码而不是MultiByteToWideChar的问题。

如果我可以在初始化时将其设置为应用程序的默认设置,可能通过实际修改Tencoding.UTF8的类var,这可能就足够了。

当然,我需要一个解决方案,而不是等待提交质量控制报告,要求更强大的设计,让它被接受,并看到它发生了变化。

任何想法都会非常受欢迎。并且有人可以确认这仍然是我尚未安装的XE4的问题吗?

4 个答案:

答案 0 :(得分:12)

当我第一次更新Indy以支持MB_ERR_INVALID_CHARS时,我遇到了TEncoding问题,并最终为UTF-8处理实现了自定义TEncoding派生类,以避免指定MB_ERR_INVALID_CHARS 1}}。我没想过要使用类助手。

但是,这个问题不仅限于UTF-8。任何TEncoding类的任何解码失败都将导致空白结果,而不是引发异常。当大多数RTL / VCL使用异常时,为什么Embarcadero选择了这条路线,这超出了我的范围。不提出错误的例外导致Indy中的大量问题必须解决。

答案 1 :(得分:3)

这可以非常简单地完成,至少在Delphi XE5中(没有检查过早期版本)。只需实例化您自己的TUTF8Encoding

procedure LoadInvalidUTF8File(const Filename: string);
var
  FEncoding: TUTF8Encoding;
begin
  FEncoding := TUTF8Encoding.Create(CP_UTF8, 0, 0); 
                      // Instead of CP_UTF8, MB_ERR_INVALID_CHARS, 0
  try
    with TStringList.Create do
    try
      LoadFromFile(Filename, FEncoding);
      // ...
    finally
      Free;
    end;
  finally
    FEncoding.Free;
  end;
end;

此处唯一的问题是新实例化的IsSingleByte的{​​{1}}属性被错误地设置为TUTF8Encoding,但此属性当前未在Delphi源中的任何位置使用。< / p>

答案 2 :(得分:1)

部分解决方法是强制UTF8编码全局禁止MB_ERR_INVALID_CHARS。对我来说,这避免了引发异常的需要,因为我发现它使得MultiByteToWideChar不太“沉默”:它实际上插入了$fffd个字符(Unicode'替换字符')然后我可以在其中找到这很重要的情况。以下代码执行此操作:

unit fixutf8;
interface
uses System.Sysutils;
type
  TUTF8fixer = class helper for Tmbcsencoding
  public
    procedure setflag0;
  end;

implementation
procedure TUTF8fixer.setflag0;
{$if CompilerVersion = 31}
asm
  XOR ECX,ECX
  MOV Self.FMBToWCharFlags,ECX
end;
{$else}
begin
  Self.FMBToWCharFlags := 0;
end;
{$endif}

procedure initencoding;
begin
  (Tencoding.UTF8 as TmbcsEncoding).setflag0;
end;

initialization
  initencoding;
end.

更有用且有原则的修复需要将对MultiByteToWideChar的调用更改为不使用MB_ERR_INVALID_CHARS,并使用此标志进行初始调用,以便在加载完成后引发异常,表示字符已被替换。

有关于此问题的相关QC报告,包括76571,79042和111980.第一个已按“设计”解决。

(编辑与德尔福柏林合作)

答案 3 :(得分:0)

您的“全局”方法并非真正全局化 - 它依赖于所有代码仅使用TUTF8Encoding的同一个实例的假设。您攻击标志字段的相同实例。

但如果通过除TUTF8Encoding以外的其他方式获取TEncoding.GetUTF8个对象,则无效,例如在XE2中,另一个方法 - TEncoding.GetEncoding(CP_UTF8) - 将创建{的新实例{1}}而不是重复使用TUTF8Encoding共享的。或者某些功能可能直接运行FUTF8

所以我建议另外两种方法。

修补类实现的方法,有点hacky。为了获得新的“修复”构造函数体,你引入了自己的类。

TUTF8Encode.Create

这个构造函数将是type TMyUTF8Encoding = class(TUTF8Encoding) public constructor Create; override; end; 实现的模仿,除了根据需要设置标志(在XE2中,它是通过调用另一个,继承的TUTF8Encoding.Create()完成的,所以你不需要访问私人领域)。

然后你可以修补股票Create(x,y,z) VMT将其虚拟构造函数覆盖到你的新构造函数。

您可以阅读有关“内部格式”等的Delphi文档,以获取VMT布局。您还需要在修补之前调用TUTF8Encoding(或其他特定于平台的函数)来删除对VMT内存区域的保护,然后再进行恢复。

学习的例子

或者您可以尝试使用 Delphi Detours 库,希望它可以修补虚拟构造函数。然后......在这里使用那个相当复杂的lib来实现这个单一目标可能会有点过头了。

在您攻击VirtualProtect类之后,请调用TUTF8Encoding以删除已创建的共享实例(如果有),从而触发重新创建UTF8实例并进行修改。

然后,如果您将程序编译为TEncoding.FreeEncodings,而不使用运行时BPL模块,则只需将single monolithic EXE源复制到应用程序文件夹,然后将该本地副本明确包含到项目中

How to patch a method in Classes.pas

在那里你可以根据你认为合适的来源更改非常SysUtils.pas实现,而Delphi会使用它。

如果你的项目被构建为重用TUTF8Encoding运行时包而不是单一的,那么这种大脑致命的简单化(因此同样可靠)的方法是行不通的。