字符串在 ComboBox.AddObject 中损坏。如何以正确的方式添加它们?

Strings getting corrupted in ComboBox.AddObject. How to add them the proper way?

我正在将带有对象(也是字符串)的字符串添加到 TComboBox,但在稍后尝试检索它们时出现损坏的字符串。

这就是我添加它们的方式:

var
  i: Integer;
  sl: TStringList;
  c: Integer;
  s: PChar;
begin

  for i := 1 to tblCalls.FieldCount do
    if tblCalls.Fields[i - 1].Tag = 1 then
      ListBox1.Items.Append(tblCalls.Fields[i - 1].FieldName);

  sl := TStringList.Create;
  try
    LoadStyles(TStrings(sl));

    for c := 0 to sl.Count - 1 do
    begin

      s := PChar(sl.Values[sl.Names[c]]);
      ComboBox1.Items.AddObject(sl.Names[c], TObject(s));
    end;

  finally
    sl.Free;
  end;

end;

procedure LoadStyles(var AStylesList: TStrings);
var
  f, n: String;
  filelist: TStringDynArray;
begin
  f := ExtractFilePath(ParamStr(0)) + 'Styles';
  if (not DirectoryExists(f)) then
    Exit;

  filelist := TDirectory.GetFiles(f);

  for f in filelist do
  begin
    n := ChangeFileExt(ExtractFileName(f), EmptyStr);
    AStylesList.Add(n + '=' + f);
  end;
end;

..这是我试图检索字符串对象的地方:

procedure TfrmOptions.ComboBox1Change(Sender: TObject);
var
  si: TStyleInfo;
  i: Integer;
  s: String;
begin
  i := TComboBox(Sender).ItemIndex;
  s := PChar(TComboBox(Sender).Items.Objects[i]);

  Showmessage(s); // --> Mostly shows a corrupted string (gibberish chars)

  if (TStyleManager.IsValidStyle(s, si)) then
  begin
    if (not MatchStr(s, TStyleManager.StyleNames)) then
      TStyleManager.LoadFromFile(s);
    TStyleManager.TrySetStyle(si.Name);
  end;
end;

我怀疑这与我添加它们的方式有关。也许我需要在以下位置分配内存:

s := PChar(sl.Values[sl.Names[c]]);

不确定。查看有关 StrNew、NewStr 和 StrAlloc 的帮助,它说这些函数已被弃用。能帮忙指出错误吗?

没有什么可以让字符串保持活动状态。当你写:

s := PChar(sl.Values[sl.Names[c]]);

创建了一个 string 类型的隐式局部变量来保存 sl.Values[sl.Names[c]] 的计算结果。该局部变量超出范围,就编译器所知,没有任何引用它,并且字符串对象被销毁。

事实上,情况比这更糟。因为上面的赋值发生在一个循环中,所以只有一个隐式局部变量。每次循环,您要求组合框记住的字符串都会被破坏。

你需要想办法延长字符串的寿命。你可以这样做:

var
  StrPtr: ^string;
....
for c := 0 to sl.Count - 1 do
begin
  New(StrPtr);
  StrPtr^ := sl.Values[sl.Names[c]];
  ComboBox1.Items.AddObject(sl.Names[c], TObject(StrPtr));
end;

然后当您需要访问字符串时,您可以这样做:

var
  StrPtr: ^string;
....
TObject(StrPtr) := TComboBox(Sender).Items.Objects[i];
// do something with StrPtr^

当您清除组合框时,您还必须 运行 遍历每个项目并在指针上调用 Dispose


话虽如此,但不这样做会容易得多。停止尝试将字符串强制放入与每个项目关联的 TObject 数据中。而是保留一个包含这些字符串的并行字符串列表。当您需要查找名称时,请在该列表中查找,而不是在组合框中查找。

我知道这是一个老问题,但我又遇到了这个问题,我没有使用单独的字符串列表,而是使用了一个带有字符串值的对象(我想有人在评论中建议了它)如下:

将类型声明为具有字符串值的 TObject:

  TStringObject = class(TObject)
    StringValue : string;
  end;

然后在添加项目时声明 TStringObject 的本地变量并为每个项目创建一个新实例:

var
  strObj : TStringObject
begin

    ...

    for c := 0 to sl.Count - 1 do
    begin
      strObj := TStringObject.Create;
      strObj.StringValue := sl.Values[sl.Names[c]];
      ComboBox1.Items.AddObject(sl.Names[c], strObj);
    end;

并且在检索值时:

s := TStringObject(TComboBox(Sender).Items.Objects[i]).StringValue;

正如@Dejan Dozet 在评论中提到的那样——您应该始终在释放 TStringList 之前释放数据对象!