【问题标题】:How to convert widestring to string of unicode bytes?如何将宽字符串转换为 unicode 字节字符串?
【发布时间】:2017-04-16 10:39:35
【问题描述】:

当我在记事本中创建一个包含(示例)字符串 1d 并保存为 unicode 文件的文件时,我得到一个包含字节 #255#254#49#0#100#0 的 6 字节大小的文件。

好的。现在我需要一个 Delphi 6 函数,它采用(示例)输入宽字符串 1d 并返回包含 #255#254#49#0#100#0 的字符串(反之亦然)。

怎么样? 谢谢。 D

【问题讨论】:

  • 看来您需要聘请一名程序员。你有没有努力自己做这件事?我们不是代码编写服务。
  • 你在这方面的哪个特定方面卡住了
  • #255#254 是 the BOM for the file (特别是 UTF-16LE) - 它与字符串本身无关。如果您尝试操作 Unicode,可能值得先了解它的工作原理。

标签: delphi unicode delphi-6


【解决方案1】:

如果使用十六进制,则更容易读取字节。 #255#254#49#0#100#0 以十六进制表示为

FF FE 31 00 64 00

在哪里

FF FEUTF-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 使用本地机器的默认 Ansi 字符集自动将 AnsiString 数据从 8 位转换为 UTF-16LE转换。

走另一条路也一样简单:

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() 函数(或具有等效功能的第 3 方库),因此您可以根据需要指定所需的字符集/代码页。

那么,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;

更新:如果您想将 UTF-16LE 编码的字节存储在 AnsiString 变量中(为什么?),那么您可以将 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;

【讨论】:

  • 谢谢。您能否举一个使用 MultiBytetowidechar() 和反之 API 来解决此问题(其他字符集)的示例?
  • 对不起,在您的第二个示例中,长度返回 2,而我预计为 4! (#49#0#100#0)。
  • 特别是我的问题需要 6 个字符的字符串!
  • @danmatei 有很多MultiByteToWideCharWideCharToMultiByte 的示例,如果您环顾四周并阅读文档。 Length() 返回元素数,而不是字节数。 WideString 使用 16 位元素,AnsiString 使用 8 位元素。 WWideStringSAnsiStringW 包含 3 个元素(BOM、1d)。转换后的AnsiString 包含 2 个元素(1d),而不是 4 或 6。
  • ANSIstring #255#254#49#0#100#0 包含 6 个元素。这就是我希望我的函数返回的内容。怎么样?
【解决方案2】:

WideString已经是一串 Unicode 字节。具体来说,在 UTF16-LE 编码中。

您在记事本保存的 Unicode 文件中看到的两个额外字节称为 BOM - Byte Order 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 字节转换为 WideString 的 UTF16-LE 编码.

  • 如果前 2 个字节是 #255#254(反之亦然),那么您正在处理没有 BOM 的 UTF16-LE 或可能完全是其他编码。

祝你好运。 :)

【讨论】:

    猜你喜欢
    • 2013-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-27
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    • 2012-11-30
    相关资源
    最近更新 更多