从RawByteString转换为字符串会自动调用UTF8Decode吗?

时间:2014-06-05 10:20:54

标签: delphi unicode encoding utf-8

我想将任意二进制数据作为BLOB存储到SQlite数据库中。

使用此功能将数据添加为value

procedure TSQLiteDatabase.AddParamText(name: string; value: string);

现在我想将WideString转换为其UTF8表示,因此可以将其存储到数据库中。在调用UTF8Encode并将结果存储到数据库之后,我注意到数据库中的数据不是UTF8解码的。相反,它在我的计算机的语言环境中被编码为AnsiString。

我按照以下测试检查发生了什么:

type
  {$IFDEF Unicode}
  TBinary = RawByteString;
  {$ELSE}
  TBinary = AnsiString;
  {$ENDIF}

procedure TForm1.Button1Click(Sender: TObject);
var
  original: WideString;
  blob: TBinary;
begin
  original := 'ä';
  blob     := UTF8Encode(original);

  // Delphi 6:   ä (as expected)
  // Delphi XE4: ä  (unexpected! How did it do an automatic UTF8Decode???)
  ShowMessage(blob);
end;

在将字符“ä”转换为UTF8之后,数据在内存中是正确的(“¤”),但是,只要我将TBinary值传递给函数({{1}或者string),Delphi XE4由于某种我不知道的原因而调用UTF8Decode进行“魔术类型转换”。

我已经找到了解决方法来避免这种情况:

AnsiString

然而,function RealUTF8Encode(AInput: WideString): TBinary; var tmp: TBinary; begin tmp := UTF8Encode(AInput); SetLength(result, Length(tmp)); CopyMemory(@result[1], @tmp[1], Length(tmp)); end; procedure TForm1.Button2Click(Sender: TObject); var original: WideString; blob: TBinary; begin original := 'ä'; blob := RealUTF8Encode(original); // Delphi 6: ä (as expected) // Delphi XE4: ä (as expected) ShowMessage(blob); end; 的这种解决方法对我来说看起来很脏,我想理解为什么简单调用RealUTF8Encode不起作用以及是否有更好的解决方案。

1 个答案:

答案 0 :(得分:7)

在Ansi的Ansi版本中(在D2009之前),UTF8Encode()返回UTF-8编码的AnsiString。在Unicode版本(D2009及更高版本)中,它返回一个UTF-8编码的RawByteString,其代码页为CP_UTF8(65001)。

在Ansi版本中,ShowMessage()输入AnsiString,UTF-8字符串为AnsiString,因此它按原样显示。在Unicode版本中,ShowMessage()采用UTF-16编码的UnicodeString作为输入,因此使用其指定的RawByteString代码页将UTF-8编码的CP-UTF8转换为UTF-16

如果您实际上将blob数据直接写入数据库,您会发现它可能是也可能不是UTF-8编码,具体取决于您的编写方式。但你的做法是错误的;在这种情况下使用RawByteString是不正确的。 RawByteString仅用作过程参数。不要将它用作局部变量。这是你问题的根源。来自documentation

  

RawByteString的目的是减少对多个的需求   读取字符串数据的过程的重载。这意味着   处理字符串的例程的参数,而不考虑   string的代码页通常应该是RawByteString类型。

     

RawByteString只能用作参数类型,而且只能用于   例程,否则AnsiStrings需要多次重载   使用不同的代码页。这样的例程需要谨慎编写   对于运行时字符串的实际代码页。

对于Unicode版本的Delphi而不是RawByteString,我建议您使用TBytes来保存您的UTF-8数据,并使用TEncoding对其进行编码:

var
  utf8: TBytes;
  str: string;
...
str := ...;
utf8 := TEncoding.UTF8.GetBytes(str);

您正在寻找一种在传递时不执行隐式文本编码的数据类型,TBytes就是那种类型。

对于Ansi的Ansi版本,您可以完全按照AnsiStringWideStringUTF8Encode使用。

但就个人而言,我建议您始终对您的UTF-8数据使用TBytes。因此,如果您需要一个支持Ansi和Unicode编译器的代码库(唉!),那么您应该创建一些帮助程序:

{$IFDEF Unicode}
function GetUTF8Bytes(const Value: string): TBytes;
begin
  Result := TEncoding.UTF8.GetBytes(Value);
end;
{$ELSE}
function GetUTF8Bytes(const Value: WideString): TBytes;
var
  utf8str: UTF8String;
begin
  utf8str := UTF8Encode(Value);
  SetLength(Result, Length(utf8str));
  Move(Pointer(utf8str)^, Pointer(Result)^, Length(utf8str));
end;
{$ENDIF}

Ansi版本会产生比必要更多的堆分配。您可能会选择编写一个更有效的帮助程序,直接调用WideCharToMultiByte()

在Unicode版本的Delphi中,如果由于某种原因您不想将TBytes用于UTF-8数据,则可以使用UTF8String代替。这是一个特殊的AnsiString,始终使用CP_UTF8代码页。然后你可以写:

var
  utf8: UTF8String;
  str: string;
....
utf8 := str;

并且编译器将在幕后为您转换为UTF-16到UTF-8。我不建议这样做,因为它在移动平台上不受支持,或者在Delphi的Ansi版本中不支持(UTF8String自Delphi 6以来就存在,但在Delphi 2009之前它不是真正的UTF-8字符串)。也就是说,除其他原因外,我建议您使用TBytes。我的理念是,至少在Unicode时代,存在本地string类型,任何其他编码都应保存在TBytes中。