我发现(困难的方法)如果文件具有有效的UTF-8 BOM但包含任何无效的UTF8编码,并且被任何Delphi(2009+)编码启用的方法读取,例如 LoadFromFile
,然后结果是一个完全空的文件,没有错误指示。在我的几个应用程序中,我宁愿丢失一些不好的编码,即使在这种情况下我也没有得到任何错误报告。
调试显示了这一点 MultiByteToWideChar
被调用两次,首先获取输出缓冲区大小,然后进行转换。但是TEncoding.UTF8包含私有 FMBToWCharFlags
这些调用的值,用a初始化 MB_ERR_INVALID_CHARS
值。因此,获取charcount的调用返回0并且加载的文件完全为空。在没有标志的情况下调用此API将“默默地删除非法代码点”。
我的问题是如何最好地编织Encoding区域中的类的嵌套来解决这个私有值的事实(并且需要,因为它是所有线程的类var)。我想我可以使用Marco Cantu的Delphi 2009书中的指南添加自定义UTF8编码。如果,它可以选择性地引发异常 MultiByteToWideChar
在没有标志的情况下再次调用它后,它返回了一个编码错误。但这并没有解决如何使用我的自定义编码而不是 Tencoding.UTF8
。
如果我可以在初始化时将其设置为应用程序的默认值,可能实际上是通过修改类var来实现的 Tencoding.UFT8
,这可能就足够了。
当然,我需要一个解决方案,而不是等待提交质量控制报告,要求更强大的设计,让它被接受,并看到它改变。
任何想法都会非常受欢迎。并且有人可以确认这仍然是XE4的一个问题,我还没有安装?
我遇到了 MB_ERR_INVALID_CHARS
当我第一次更新Indy以支持时的问题 TEncoding
,最终实现了一个自定义 TEncoding
- 用于UTF-8处理的派生类,以避免指定 MB_ERR_INVALID_CHARS
。我没想过要使用类助手。
但是,这个问题不仅限于UTF-8。任何解码失败的任何一个 TEncoding
类将导致空白结果,而不是引发异常。当大多数RTL / VCL使用异常时,为什么Embarcadero选择了这条路线,这超出了我的范围。不提出错误的例外导致Indy中的大量问题必须解决。
这可以非常简单地完成,至少在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源中的任何位置使用。
部分解决方法是强制禁止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.第一个已按“设计”解决。
(编辑与德尔福柏林合作)
你的“全局”方法并不是真正的全局 - 它依赖于所有代码只使用同一个实例的假设 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)
所以你不需要访问私有领域)。
然后你可以修补股票 TUTF8Encoding
VMT将其虚拟构造函数重写为您的新构造函数。
您可以阅读有关“内部格式”等的Delphi文档,以获取VMT布局。你还需要打电话 VirtualProtect
(或其他特定于平台的功能)在修补之前从VMT内存区域删除保护,然后恢复它。
要学习的例子
或者您可以尝试使用 Delphi Detours 库,希望它可以修补虚拟构造函数。然后......在这里使用那个相当复杂的lib来实现这个单一目标可能是一种过度杀伤力。
你砍了之后 TUTF8Encoding
上课打电话给 TEncoding.FreeEncodings
删除已创建的共享实例(如果有),从而触发使用您的修改重新创建UTF8实例。
然后,如果您将程序编译为 single monolithic EXE
,不使用运行时BPL模块,你只需要复制 SysUtils.pas
源到您的应用程序文件夹,然后明确地将该本地副本包含到您的项目中。
如何在Classes.pas中修补方法
你会改变它 TUTF8Encoding
您认为适合源代码的实现,Delphi将使用它。
如果您的项目可以重新构建,那么这种大脑致命的简单(因此同样可靠)方法将无法工作 rtlNNN.bpl
运行时包而不是单片。