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

Dan*_*all 5 delphi unicode encoding utf-8

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

value使用此功能添加数据:

procedure TSQLiteDatabase.AddParamText(name: string; value: string);
Run Code Online (Sandbox Code Playgroud)

现在我想将a 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;
Run Code Online (Sandbox Code Playgroud)

在字符"ä"转换为UTF8之后,数据在内存中是正确的("¤"),但是,只要我将TBinary值传递给函数(作为stringAnsiString),Delphi XE4就会进行"魔术类型转换"因某些原因调用UTF8Decode我不知道.

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

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;
Run Code Online (Sandbox Code Playgroud)

但是,这种解决方法RealUTF8Encode看起来很脏,我想了解为什么简单的调用UTF8Encode不起作用,如果有更好的解决方案.

Dav*_*nan 7

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

以ANSI版本中,ShowMessage()需要一个AnsiString作为输入,和UTF-8字符串是一个AnsiString,因此它获取显示原样.在Unicode版本中,ShowMessage()采用UTF-16编码UnicodeString作为输入,因此UTF-8编码RawByteString使用其指定的CP-UTF8代码页转换为UTF-16 .

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

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

RawByteString只能用作参数类型,并且只能用于具有不同代码页的AnsiStrings需要多次重载的例程.这些例程需要在运行时小心写入字符串的实际代码页.

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

var
  utf8: TBytes;
  str: string;
...
str := ...;
utf8 := TEncoding.UTF8.GetBytes(str);
Run Code Online (Sandbox Code Playgroud)

您正在寻找一种在传递时不执行隐式文本编码的数据类型,并且TBytes是该类型.

德尔福的安思版本,你可以使用AnsiString,WideString并且UTF8Encode完全按照你做的.

但就个人而言,我建议TBytes一致地使用您的UTF-8数据.因此,如果您需要一个支持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}
Run Code Online (Sandbox Code Playgroud)

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

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

var
  utf8: UTF8String;
  str: string;
....
utf8 := str;
Run Code Online (Sandbox Code Playgroud)

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