First of all, sorry for the lengthy code example, but I believe it's needed to illustrate my problem.
As a debugging help I often introduce a "DebugString"-method on my objects, which returns a concise object summary. But sometimes my objects are too complex to be represented optimally in a single string, so I use stringlists. Now, I would like to use the excellent debug visualizers in Delphi to monitor my object. The way I do this is to introduce a property with a getter that rebuilds the stringlist.
This kinda works, but for every line I trace, the property gets out of scope, so I have to click the magnifying glass in the watch window again to see the value. Why is this?
To reproduce, create a new console application:
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.
Why does the visualizer say that the watch is out of scope? How can I fix this?
PS: I know I can write debug visualizers, but I use "DebugString" and "DebugStringList" in some automatic tests, and I would really like to use them in this easy fashion.
Update: I use Delphi XE
Update 2: Despite a good effort by Marjan Venema, I still have no solution to this problem. I've filed a report with Embarcadero (QC number 98062, please vote :-)). However, I suspect it will take some time for Embarcadero fix this problem, and seeing how I'm still interested in a workaround, I'll offer a small bounty. Never tried that before, so it will be interesting to se what happens :-)
It goes out of scope because that is exactly what happens when Scramble is executed. The bug maybe in that the visualizer does not get refreshed when it comes back into scope. Haven't had a look at the TStrings visualizer yet, but a work-around is to use an untyped pointer variable to the FDebugStringList and put a watch on a typecast of that TStringList to that derefenced pointer.
var
vMyObj : TMyClass;
vSL: Pointer;
{$OPTIMIZATION OFF}
begin
vMyObj := TMyClass.Create;
try
vSL := @(vMyObj.FDebugStringList);
and the watch on:
TStringList(vSL^)
When you now break on the second scramble, open the visualizer for vSL, you'll see the contents of FDebugStringList. When you step over that second scramble, you can see the visualizer "thinking while scramble executes and then refreshing itself as you come back to the main level".
Pitfall: you do need to make sure the untyped pointer variable doesn't get optimized away. So either make some non-trivial use of it make sure optimization is off while debugging.
Edit: Unfortunately it seems the work around shows outdated values. See comment by Svein.
Update
Disclaimer: I am no ToolsAPI expert. A cursory examination of StringListVisualizer and the ToolsAPI units shows that RefreshVisualizer is "{ Called when the data for the visualizer needs to be refreshed }". Furthermore, the string "RefreshVisualizer" is only found in the interface declaration in the ToolsAPI unit and declared and implemented in the StringListVisualizer unit. So my guess at the moment is that the debugger should call RefreshVisualizer when coming back to stop on the third Scramble call, but doesn't. Worth a QC report in my opinion. At the very least it is a "User experience shortcoming".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With