直接在其地址(或“Unicode ShortString”)存储的Delphi Unicode字符串类型

时间:2010-05-10 21:19:18

标签: delphi string memory unicode

我想要一个Unicode类型的字符串类型,它将字符串直接存储在变量的地址中,就像(仅限Ansi)ShortString类型一样。

我的意思是,如果我声明S: ShortString并让S := 'My String',那么,在@S,我会找到字符串的长度(作为一个字节,因此字符串不能包含超过255个字符)后跟ANSI编码的字符串本身。

我想要的是这种Unicode变体。也就是说,我想要一个字符串类型,在@S,我会找到一个无符号的32位整数(或者实际上是单个字节就足够了),包含字符串的长度(以字节为单位) ,这是字节数的一半),后跟字符串的Unicode表示。我已经尝试了WideStringUnicodeStringRawByteString,但它们似乎只是在@S存储地址,而在其他地方存储实际字符串(我想这可以做到)做参考计数等)。 更新:最重要的原因可能是如果sizeof(字符串)是可变的,那将是非常有问题的。

我怀疑没有内置类型可供使用,而且我必须按照自己想要的方式(实际上很有趣)提出我自己的文本存储方式。我是对的吗?

更新 除其他事项外,我将需要在打包记录中使用这些字符串。我还需要手动将这些字符串读/写到文件/堆中。我可以使用固定大小的字符串,例如< = 128个字符,我可以重新设计问题,以便它可以使用以null结尾的字符串。但是PChar不会起作用,因为sizeof(PChar)= 1 - 它只是一个地址。

我最终解决的方法是使用静态字节数组。我将在今天晚些时候发布我的实施解决方案。

5 个答案:

答案 0 :(得分:4)

你是对的。与保留Unicode字符的ShortString没有确切的类比。有许多事情接近,包括WideStringUnicodeStringWideChar数组,但如果您不愿意重新审视您打算使用数据类型的方式(在内存和文件中进行逐字节复制,同时仍然在所有上下文中使用它们,可以允许字符串),那么Delphi的内置类型都不适合你。

WideString失败,因为您坚持字符串的长度必须存在于地址字符串变量,但WideString是引用类型;其地址唯一的另一个地址。它的长度恰好位于变量所持的地址,减去4。但是,这可能会发生变化,因为该类型的所有操作都应该通过API。

UnicodeString因同样的原因失败,也因为它是引用计数类型;制作一个逐字节的副本会破坏引用计数,因此您将获得内存泄漏,无效指针操作异常或更细微的堆损坏。

可以毫无问题地复制WideChar数组,但它不会跟踪其有效长度,并且它也不会经常表现为字符串。您可以为其分配字符串文字,它的行为就像您调用StrLCopy一样,但您不能将字符串变量分配给它。

您可以定义一个记录,该记录包含长度字段和字符数组的另一个字段。这将解决长度问题,但它仍然会有未修饰数组的所有其他缺点。

如果我是你,我只是使用内置字符串类型。然后我会编写函数来帮助在文件,内存块和本机变量之间传输它。这并不难;可能比尝试使用自定义记录类型使操作符重载正常工作容易得多。考虑一下您将编写多少代码来加载和存储数据,以及您要编写的代码使用您的数据结构(如普通字符串)。您将编写一次数据持久性代码,但是对于项目的剩余生命周期,您将使用这些字符串,并且您将希望它们看起来和就像真正的字符串一样。所以用真正的字符串。 “痛苦”手动生成所需的磁盘格式带来的不便,并获得能够使用所有现有字符串库函数的优势。

答案 1 :(得分:1)

PChar应该这样工作,对吗? AFAIK,它是存储在你放置它的地方的一系列字符。零终止,不确定如何使用Unicode Chars。

答案 2 :(得分:1)

你实际上在某种程度上使用了新的unicode字符串 s作为指向s [1]的指针,左边的4个字节包含长度 但为什么不简单地使用长度?

直接从记忆中读取长度:

procedure TForm9.Button1Click(Sender: TObject);
var
  s: string;
begin
  s := 'hlkk ljhk jhto';
  {$POINTERMATH ON}
  Assert(Length(s) = (PInteger(s)-1)^); 
  //if you don't want POINTERMATH, replace by PInteger(Cardinal(s)-SizeOf(Integer))^
  showmessage(IntToStr(length(s)));
end;

答案 3 :(得分:1)

没有Unicode版本的ShortString。如果要将unicode数据内联存储在对象内而不是作为引用类型,则可以分配缓冲区:

var
  buffer = array[0..255] of WideChar;

这有两个缺点。 1,大小是固定的,2,编译器不会将其识别为字符串类型。

这里的主要问题是#1:固定大小。如果您要在更大的对象或记录中声明一个数组,编译器需要知道它有多大,以便计算对象或记录本身的大小。对于ShortString来说,这不是一个大问题,因为它们总共只能达到256字节(1/4 K),这并不是那么多。但是,如果要使用由32位整数寻址的长字符串,则最大大小为4 GB。你不能把它放在一个对象里面!

这不是引用计数,而是将长字符串实现为引用类型的原因,其内联大小始终是常量sizeof(指针)。然后编译器可以将字符串数据放在动态数组中并调整大小以适应当前需要。

为什么你需要把这样的东西放到一个打包的数组中?如果我猜,我会说这可能与序列化有关。如果是这样,最好使用TStream和普通的Unicode字符串,并将整数(大小)写入流,然后写入字符串的内容。事实证明,这比将所有内容都填充到打包数组中要灵活得多。

答案 4 :(得分:0)

我最终解决的解决方案是这个(真实世界的样本 - 字符串当然是第三个名为“Ident”的成员):

TASStructMemHeader = packed record
  TotalSize: cardinal;
  MemType: TASStructMemType;
  Ident: packed array[0..63] of WideChar;
  DataSize: cardinal;
  procedure SetIdent(const AIdent: string);
  function ReadIdent: string;
end;

,其中

function TASStructMemHeader.ReadIdent: string;
begin
  result := WideCharLenToString(PWideChar(@(Ident[0])), length(Ident));
end;

procedure TASStructMemHeader.SetIdent(const AIdent: string);
var
  i: Integer;
begin
  if length(AIdent) > 63 then
    raise Exception.Create('Too long structure identifier.');
  FillChar(Ident[0], length(Ident) * sizeof(WideChar), 0);
  Move(AIdent[1], Ident[0], length(AIdent) * sizeof(WideChar));
end;

但后来我意识到编译器真的可以将array[0..63] of WideChar解释为字符串,所以我可以简单地写一下

  var
    MyStr: string;

  Ident := 'This is a sample string.';
  MyStr := Ident;

因此,毕竟,Mason Wheeler给出的答案实际上是 答案。