13

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

调试显示MultiByteToWideChar调用了两次,首先是获取输出缓冲区大小,然后是进行转换。但是 TEncoding.UTF8 包含FMBToWCharFlags用于这些调用的私有值,并且这是用一个MB_ERR_INVALID_CHARS值初始化的。所以获取字符数的调用返回 0 并且加载的文件是完全空的。在没有标志的情况下调用此 API 将“默默地丢弃非法代码点”。

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

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

当然,我需要一个解决方案,而无需等待提交 QC 报告,要求提供更强大的设计,让其接受并看到它发生变化。

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

4

4 回答 4

12

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

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

于 2013-05-14T01:41:37.603 回答
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新实例化的属性TUTF8Encoding被错误地设置为False,但这个属性目前在 Delphi 源代码中的任何地方都没有使用。

于 2014-07-29T04:46:50.413 回答
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.

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

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

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

于 2013-05-14T08:57:05.560 回答
0

您的“全局”方法并不是真正的全局 - 它依赖于所有代码将仅使用一个且相同的TUTF8Encoding. 您入侵标志字段的同一实例。

TUTF8Encoding但是,如果通过其他方式获取对象TEncoding.GetUTF8,例如在 XE2 中,另一种方法 - TEncoding.GetEncoding(CP_UTF8)- 将创建一个新实例TUTF8Encoding而不是重新使用FUTF8共享的实例,则它将不起作用。或者某些功能可能会TUTF8Encode.Create直接运行。

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

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

type TMyUTF8Encoding = class(TUTF8Encoding)
  public constructor Create; override;
end;

这个构造函数将是TUTF8Encoding.Create()实现的复制品,除了根据需要设置标志(在 XE2 中,它是通过调用另一个继承来完成的,Create(x,y,z)因此您不需要访问私有字段)。

然后,您可以将覆盖其虚拟构造函数的股票TUTF8EncodingVMT 修补到您的新构造函数。

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

学习的例子

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

在您破解TUTF8Encoding该类后,请调用TEncoding.FreeEncodings删除已创建的共享实例(如果有)(如果有),从而触发使用您的修改重新创建 UTF8 实例。


然后,如果您将程序编译为single monolithic EXE.而不使用运行时 BPL 模块,您只需将SysUtils.pas源代码复制到您的应用程序文件夹,然后将该本地副本显式包含到您的项目中。

如何修补 Classes.pas 中的方法

在那里,您将更改TUTF8Encoding源代码中您认为合适的实现,Delphi 将使用它。

rtlNNN.bpl如果您的项目将被构建为重用运行时包而不是单一的,那么这种致命的简单化(因此 - 同样可靠)的方法将不起作用。

于 2017-01-16T15:33:03.313 回答