当我在记事本中创建一个文件时,包含(示例)字符串1d
并保存为unicode文件,我得到一个包含字节#255#254#49#0#100#0
的6字节大小的文件。
行。现在我需要一个Delphi 6函数,它接受(示例)输入宽字符串1d
并返回包含#255#254#49#0#100#0
的字符串(反之亦然)。
如何? 谢谢。 d
答案 0 :(得分:5)
如果使用十六进制,则更容易读取字节。 #255#254#49#0#100#0
以十六进制表示为
FF FE 31 00 64 00
其中
FF FE
是UTF-16LE BOM,它使用Little Endian中的值将以下字节标识为UTF-16。
31 00
是ASCII字符'1'
64 00
是ASCII字符'd'
。
创建包含这些字节的WideString
非常简单:
var
W: WideString;
S: String;
begin
S := '1d';
W := WideChar($FEFF) + S;
end;
当AnsiString
(Delphi 6的默认字符串类型)被分配给WideString
时,RTL会自动将AnsiString
数据从8位转换为UTF-16LE。本地机器的默认Ansi字符集用于转换。
走另一条路也很简单:
var
W: WideString;
S: String;
begin
W := WideChar($FEFF) + '1d';
S := Copy(W, 2, MaxInt);
end;
当您将WideString
分配给AnsiString
时,RTL会使用默认的Ansi字符集自动将WideString
数据从UTF-16LE转换为8位。
如果默认的Ansi字符集不适合您的需求(比如8位数据需要在不同的字符集中编码),则必须使用Win32 API MultiByteToWideChar()
和WideCharToMultiByte()
函数直接(或具有同等功能的第三方库),因此您可以根据需要指定所需的字符集/代码页。
现在,Delphi 6没有提供任何有用的助手来读取Unicode文件(Delphi 2009及更高版本),所以你必须自己手动完成,例如:
function ReadUnicodeFile(const FileName: string): WideString;
const
cBOM_UTF8: array[0..2] of Byte = ($EF, $BB, $BF);
cBOM_UTF16BE: array[0..1] of Byte = ($FE, $FF);
cBOM_UTF16LE: array[0..1] of Byte = ($FF, $FE);
cBOM_UTF32BE: array[0..3] of Byte = ($00, $00, $FE, $FF);
cBOM_UTF32LE: array[0..3] of Byte = ($FF, $FE, $00, $00);
var
FS: TFileStream;
BOM: array[0..3] of Byte;
NumRead: Integer;
U8: UTF8String;
U32: UCS4String;
I: Integer;
begin
Result := '';
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
NumRead := FS.Read(BOM, 4);
// UTF-8
if (NumRead >= 3) and CompareMem(@BOM, @cBOM_UTF8, 3) then
begin
if NumRead > 3 then
FS.Seek(-(NumRead-3), soCurrent);
SetLength(U8, FS.Size - FS.Position);
if Length(U8) > 0 then
begin
FS.ReadBuffer(PAnsiChar(U8)^, Length(U8));
Result := UTF8Decode(U8);
end;
end
// the UTF-16LE and UTF-32LE BOMs are ambiguous! Check for UTF-32 first...
// UTF-32
else if (NumRead = 4) and (CompareMem(@BOM, cBOM_UTF32LE, 4) or CompareMem(@BOM, cBOM_UTF32BE, 4)) then
begin
// UCS4String is not a true string type, it is a dynamic array, so
// it must include room for a null terminator...
SetLength(U32, ((FS.Size - FS.Position) div SizeOf(UCS4Char)) + 1);
if Length(U32) > 1 then
begin
FS.ReadBuffer(PUCS4Chars(U32)^, (Length(U32) - 1) * SizeOf(UCS4Char));
if CompareMem(@BOM, cBOM_UTF32BE, 4) then
begin
for I := Low(U32) to High(U32) do
begin
U32[I] := ((U32[I] and $000000FF) shl 24) or
((U32[I] and $0000FF00) shl 8) or
((U32[I] and $00FF0000) shr 8) or
((U32[I] and $FF000000) shr 24);
end;
end;
U32[High(U32)] := 0;
// Note: UCS4StringToWidestring() does not actually support UTF-16,
// only UCS-2! If you need to handle UTF-16 surrogates, you will
// have to convert from UTF-32 to UTF-16 manually, there is no RTL
// or Win32 function that will do it for you...
Result := UCS4StringToWidestring(U32);
end;
end
// UTF-16
else if (NumRead >= 2) and (CompareMem(@BOM, cBOM_UTF16LE, 2) or CompareMem(@BOM, cBOM_UTF16BE, 2)) then
begin
if NumRead > 2 then
FS.Seek(-(NumRead-2), soCurrent);
SetLength(Result, (FS.Size - FS.Position) div SizeOf(WideChar));
if Length(Result) > 0 then
begin
FS.ReadBuffer(PWideChar(Result)^, Length(Result) * SizeOf(WideChar));
if CompareMem(@BOM, cBOM_UTF16BE, 2) then
begin
for I := 1 to Length(Result) then
begin
Result[I] := WideChar(
((Word(Result[I]) and $00FF) shl 8) or
((Word(Result[I]) and $FF00) shr 8)
);
end;
end;
end;
end
// something else, assuming UTF-8
else
begin
if NumRead > 0 then
FS.Seek(-NumRead, soCurrent);
SetLength(U8, FS.Size - FS.Position);
if Length(U8) > 0 then
begin
FS.ReadBuffer(PAnsiChar(U8)^, Length(U8));
Result := UTF8Decode(U8);
end;
end;
finally
FS.Free;
end;
end;
更新:如果你想在AnsiString
变量中存储UTF-16LE编码的字节(为什么?),那么你可以Move()
WideString
个字符的原始字节数据进入AnsiString
的内存块:例如:
function WideStringAsAnsi(const AValue: WideString): AnsiString;
begin
SetLength(Result, Length(AValue) * SizeOf(WideChar));
Move(PWideChar(AValue)^, PAnsiChar(Result)^, Length(Result));
end;
var
W: WideString;
S: AnsiString;
begin
W := WideChar($FEFF) + '1d';
S := WideStringAsAnsi(W);
end;
我不建议像这样误用AnsiString
。如果需要字节,则按字节操作,例如:
type
TBytes = array of Byte;
function WideStringAsBytes(const AValue: WideString): TBytes;
begin
SetLength(Result, Length(AValue) * SizeOf(WideChar));
Move(PWideChar(AValue)^, PByte(Result)^, Length(Result));
end;
var
W: WideString;
B: TBytes;
begin
W := WideChar($FEFF) + '1d';
B := WideStringAsBytes(W);
end;
答案 1 :(得分:1)
WideString
已经一串Unicode字节。具体来说,在 UTF16-LE 编码。
您在记事本保存的Unicode文件中看到的两个额外字节称为 BOM - B yte O rder M 强>柜。这是Unicode中的一个特殊字符,用于指示后面数据中的字节顺序,以确保正确解码字符串。
将BOM添加到字符串(这是您要求的)只需要使用该特殊BOM字符预先修复字符串。 BOM字符为 U + FEFF (这是'字符'的十六进制表示的Unicode表示法。)
所以,你需要的功能非常简单:
function WideStringWithBOM(aString: WideString): WideString;
const
BOM = WideChar($FEFF);
begin
result := BOM + aString;
end;
然而,虽然功能很简单,但这可能不是问题的终点。
从此函数返回的字符串将包含BOM,并且就任何Delphi代码而言,BOM将被视为字符串的一部分。
如果没有其他机制来指示您使用的编码,通常只有在将字符串传递给某个外部收件人时(例如通过文件或Web服务响应)才能将BOM添加到字符串中。
同样,当从一些可能是Unicode的接收数据中读取字符串时,您应检查前两个字节:
如果您找到#255#254 ($ FFFE),那么您就知道U + FEFF BOM中的字节已被切换(U + FFFE不是有效的Unicode字符) 。即后面的字符串是UTF16- LE 。因此,对于Delphi WideString
,您可以丢弃前两个字节并将剩余的字节直接加载到合适的WideString
变量中。
如果您发现#254#255 ,那么U + FEFF BOM中的字节已经未被切换。即你知道后面的字符串是UTF16- BE 。在这种情况下,您再次需要丢弃前两个字节,但是当将剩余字节加载到WideString
时,您必须切换每对字节以将UTF16-BE字节转换为{{1}的UTF16-LE编码{1}}。
如果前2个字节是#255#254(反之亦然),那么您要么在没有BOM的情况下处理UTF16-LE,要么完全不需要其他编码。