我想将任意二进制数据作为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
不起作用以及是否有更好的解决方案。
答案 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版本,您可以完全按照AnsiString
,WideString
和UTF8Encode
使用。
但就个人而言,我建议您始终对您的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
中。