Base64字符串和Streams的Unicode问题

时间:2013-10-01 09:44:50

标签: delphi delphi-xe

我一直在Lazarus开展一个项目,并决定暂时将其转移到Delphi XE(由于一些限制)。

简要概述正在发生的事情:

在运行时,我正在加载外部文件并将它们添加到流中。这些流属于从一个主对象(TObject)下降的几个不同的类。这些类从主对象添加到TList,基本上每个类都有自己的流属性,而类是主对象的子类。

在这个主要对象中,我有一个保存和加载过程:

保存对象时,它还会使用字符串流将所有流数据从其他类保存到文件。这里的输出字符串必须是base64编码的,因为我保存为XML。

打开文件时,想法是解码base64字符串并将其移回流中,就好像它是base64编码之前的原始文件一样。

在Lazarus中它起作用,这是重要的代码(注意,其中一些不是由我写的)。

const
  Keys64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/';

function Encode64String(S: string): string;
function Decode64String(S: string): string;
function Encode64StringToStream(const Input: TStream; var Output: string): Boolean;
procedure Decode64StringToStream(const Input: string; Output: TStream);
procedure StringToStream(Stream: TStream; const S: string);
function StreamToString(MS: TMemoryStream): string;

implementation

function Encode64String(S: string): string;
var
  i: Integer;
  a: Integer;
  x: Integer;
  b: Integer;
begin
  Result := '';
  a := 0;
  b := 0;
  for i := 1 to Length(s) do
  begin
    x := Ord(s[i]);
    b := b * 256 + x;
    a := a + 8;
    while a >= 6 do
    begin
      a := a - 6;
      x := b div (1 shl a);
      b := b mod (1 shl a);
      Result := Result + Keys64[x + 1];
    end;
  end;
  if a > 0 then
  begin
    x := b shl (6 - a);
    Result := Result + Keys64[x + 1];
  end;
end;

function Decode64String(S: string): string;
var
  i: Integer;
  a: Integer;
  x: Integer;
  b: Integer;
begin
  Result := '';
  a := 0;
  b := 0;
  for i := 1 to Length(s) do
  begin
    x := Pos(s[i], Keys64) - 1;
    if x >= 0 then
    begin
      b := b * 64 + x;
      a := a + 6;
      if a >= 8 then
      begin
        a := a - 8;
        x := b shr a;
        b := b mod (1 shl a);
        x := x mod 256;
        Result := Result + chr(x);
      end;
    end
    else
      Exit;
  end;
end;

function Encode64StringToStream(const Input: TStream; var Output: string): Boolean;
var
  MS: TMemoryStream;
begin
  Result := False;

  MS := TMemoryStream.Create;
  try
    Input.Seek(0, soFromBeginning);
    MS.CopyFrom(Input, Input.Size);
    MS.Seek(0, soFromBeginning);
    Output := Encode64String(StreamToString(MS));
  finally
    MS.Free;
  end;

  Result := True;
end;    

procedure Decode64StringToStream(const Input: string; Output: TStream);
var
  MS: TMemoryStream;
begin
  try
    MS := TMemoryStream.Create;
    try
      StringToStream(MS, Decode64String(Input));

      MS.Seek(0, soFromBeginning);
      Output.CopyFrom(MS, MS.Size);
      Output.Position := 0;
    finally
      MS.Free;
    end;

  except on E: Exception do
    raise Exception.Create('stream decode error - ' + E.Message);
  end;
end;

procedure StringToStream(Stream: TStream; const S: string);
begin
  Stream.Write(Pointer(S)^, Length(S));
end;

function StreamToString(MS: TMemoryStream): string;
begin
  SetString(Result, PChar(MS.Memory), MS.Size div SizeOf(Char));
end;

我99%肯定这里的问题与unicode有关。这是一种耻辱,因为我相信Lazarus / Freepascal一直是unicode而不是Delphi,所以使用不同的字符串类型,这对于像我这样不那么专业的用户来说几乎是不可能解决的!

老实说,我认为上面的所有代码都有点混乱,感觉就像我只想在不知道自己在做什么的情况下猜测要改变字符串的内容。

我的第一个想法是改变从StringAnsiString的所有内容。这几乎工作了一次,但在尝试使用Decode64StringToStream时我得到的数据为零。其他时候数据没有正确保存为base64编码格式,有时我甚至会遇到像TStream.Seek没有实现的错误。

PS,我已经阅读了这些指南,还有很多关于如何将旧的Delphi项目迁移到更新的unicode版本的白皮书等等,说实话我仍然对此感到茫然。我认为将string替换为AnsiString就足够了,但似乎不是。

非常感谢任何提示,指示或一般建议或线索。

1 个答案:

答案 0 :(得分:4)

我认为你想要做的是:

  1. 将Unicode字符串转换为UTF-8编码。这通常是Unicode文本最节省空间的格式。
  2. 使用base64对字符串进行编码。
  3. 然后解码你只需要反转步骤。

    代码如下所示:

    function Encode(const Input: string): AnsiString;
    var
      utf8: UTF8String;
    begin
      utf8 := UTF8String(Input);
      Result := EncdDecd.EncodeBase64(PAnsiChar(utf8), Length(utf8));
    end;
    
    function Decode(const Input: AnsiString): string;
    var
      bytes: TBytes;
      utf8: UTF8String;
    begin
      bytes := EncdDecd.DecodeBase64(Input);
      SetLength(utf8, Length(bytes));
      Move(Pointer(bytes)^, Pointer(utf8)^, Length(bytes));
      Result := string(utf8);
    end;