为什么有些属性在监视列表中超出作用域,而其他一些则没有?

5

首先,抱歉代码示例有点长,但我认为它需要来说明我的问题。

作为调试帮助,我经常在我的对象上引入一个“DebugString”方法,它返回一个简洁的对象摘要。但是有时我的对象太复杂了,无法用单个字符串最优地表示,因此我使用字符串列表。现在,我想使用Delphi中出色的调试可视化工具来监视我的对象。我这样做的方式是引入一个带有getter的属性,用于重建字符串列表。

这种方法有点可行,但每次我跟踪一行代码时,属性就会超出范围,所以我必须再次点击观察窗口中的放大镜才能看到值。这是为什么呢?

要重现,请创建一个新的控制台应用程序:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TMyClass = class
  private
    FInternalData : array[0..4] of integer;
    FDebugStringList : TStringList;
    procedure RebuildDebugStringlist;
    function GetDebugStringList: TStringList;
    function GetDebugString : string;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Scramble;
    property DebugStringList : TStringList read GetDebugStringList;
    property DebugString : string read GetDebugString;
  end;

constructor TMyClass.Create;
begin
  FDebugStringList := TStringList.Create;
end;

destructor TMyClass.Destroy;
begin
  FDebugStringList.Free;
  inherited;
end;

function TMyClass.GetDebugString: string;
var
  I : integer;
begin
  Result := 'Object state: ';
  for I := 0 to 3 do
    Result := Result + inttostr(FInternalData[I])+' ';
end;

function TMyClass.GetDebugStringList: TStringList;
begin
  RebuildDebugStringlist;
  Result := FDebugStringlist;
end;

procedure TMyClass.RebuildDebugStringlist;
var
  I : integer;
begin
  FDebugStringList.Clear;

  FDebugStringList.Add('Object state:');
  for I := 0 to 4 do
    FDebugStringList.Add(inttostr(FInternalData[I]));
end;

procedure TMyClass.Scramble;
var
  I : integer;
begin
  for I := 0 to 4 do
    FInternalData[I] := Random(100);
end;

var
  vMyObj : TMyClass;

begin
  vMyObj := TMyClass.Create;
  try
    vMyObj.Scramble;
    vMyObj.Scramble;
    vMyObj.Scramble;
  finally
    vMyObj.Free;
  end;

  readln;
end.
  1. 为 "vMyObj.DebugStringList" 和 "vMyObj.DebugString" 添加监视器
  2. 在第77行(第二个 "vMyObj.Scramble")上设置断点,然后运行。
  3. 单击“DebugStringList”旁边的放大镜以获取可视化工具
  4. 观察可视化工具的良好运作 :)
  5. 跳过下一行。现在,可视化工具指示监视器超出了范围。
  6. 再次按下放大镜以查看对象的新状态。

为什么可视化工具会显示监视器超出了范围?我该如何解决这个问题?

PS:我知道我可以编写调试可视化工具,但我在某些自动测试中使用“DebugString”和“DebugStringList”,因此我真的希望能够以这种简单的方式使用它们。

更新:我使用 Delphi XE

更新2:尽管 Marjan Venema 做出了很好的努力,但我仍然没有解决这个问题。我已向 Embarcadero 提交了报告(QC 号码 98062,请投票 :-))。然而,我怀疑 Embarcadero 解决这个问题需要一些时间,而且考虑到我仍然对解决方法感兴趣,我将提供一小笔赏金。以前从未尝试过,所以很有趣。


1
你在这里使用的是哪个版本的 Delphi? - Mason Wheeler
我使用的是XE(确切版本为15.0.3890.34076)。 - Svein Bringsli
3个回答

4

它超出了范围,因为这正是Scramble执行时发生的情况。可能的错误是可视化工具在重新进入作用域时没有刷新。还没有查看TStrings视觉化器,但一个解决方法是使用未类型化指针变量到FDebugStringList和将一个类型转换的观察点放在derefenced指针上的TStringList。

var
  vMyObj : TMyClass;
  vSL: Pointer;

{$OPTIMIZATION OFF}
begin
  vMyObj := TMyClass.Create;
  try
    vSL := @(vMyObj.FDebugStringList);

并监视:

TStringList(vSL^)

当您在第二次乱序时打断点,打开vSL的可视化工具,您将看到FDebugStringList的内容。当您跨过第二次乱序时,您可以看到可视化工具“在乱序执行时思考并在回到主层级时刷新自身”。
陷阱:您确实需要确保未命名指针变量不会被优化掉。因此,在调试时要么对其进行一些非平凡的使用,要么确保关闭优化。
编辑:不幸的是,解决方法似乎显示过时的值。请参见Svein的评论。
更新
免责声明:我不是ToolsAPI专家。对StringListVisualizer和ToolsAPI单位的简要检查显示RefreshVisualizer是"{Called when the data for the visualizer needs to be refreshed}"。此外,在ToolsAPI单位的接口声明中仅找到字符串“RefreshVisualizer”,并在StringListVisualizer单位中声明和实现。因此,我目前的猜测是调试器应该在返回停止第三个Scramble调用时调用RefreshVisualizer,但没有。在我看来,这值得QC报告。至少它是一个“用户体验缺陷”。

感谢您的回答。不幸的是,这种解决方法并不能完全解决问题。在可视化器中显示的值已经过时了。如果您同时查看DebugStringList和DebugString,您会发现这些值是不同的。当您跟踪下一个拼字游戏时,来自DebugString的值会显示在DebugStringList中,而真实的值则显示在DebugString中。 - Svein Bringsli
@Svein: 哎呀,真是糟糕,我没有注意到那个问题。我想你现在可能陷入困境了。如果你向 Embarcadero 提交一个质量控制报告,并告诉我它的编号,我会为你投票支持的。 - Marjan Venema
谢谢您的尝试。但看起来您是正确的,我卡住了。我已经向Embarcadero提交了一份报告。QC号码是98062。 - Svein Bringsli
@Svein:好的,已投票并在评论中添加了此问题的链接。 - Marjan Venema

1

也许可视化工具在关闭堆栈帧时处理不太好,而短的getter函数是无堆栈帧的?

尝试关闭优化并打开堆栈帧,看看是否有所帮助,并将结果添加到您的QC中。


优化已经关闭,关闭堆栈帧也没有帮助。 - Svein Bringsli
堆栈帧应该开启。但是没关系,已经测试过了 :-) - Marco van de Voort

1
根据 Delphi的字符串是不可变的吗?,Delphi的字符串是“写时复制”的 - 因此,在过程RebuildDebugStringlist内更改FDebugStringList可能会丢弃旧字符串,导致其超出范围。
您是否可以尝试直接修改字符串(使用指针算术等)以重用相同的副本,并查看是否有效?当然,这假定您事先知道调试输出的最大长度并可以相应地设置初始字符串长度。

我不知道这是否可行,但即使它可行,也不是最优解决方案。我想使用可视化工具来查看一个字符串列表,而不是一个字符串。 - Svein Bringsli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接