TStringList Visualizer revised (2010, XE, XE2)

I do really like the debugger visualizers that have been added in the Delphi 2010 release including the OTA enhancement that lets you create your own visualizers. Sometimes I still use Delphi 2007 and I look for the visualizer glyph for the TStringList, do not see it, but then I do realize that I am currently in Delphi 2007. However there are things in the built in TStrings visualizer that bug me. One thing is that it is only usable for small stringlists (larger get truncated) and the other is the performance. To solve these problems I have modified the TStringList visualizer and this is what this post is all about.

The problem

Why does it truncate bigger stringlists and is slow?

The visualizer does this to get the stringlist content:

  • Get TStrings.Delimiter via the debugger/compilers evaluation feature (IOTAThread.Evaluate)
  • XE2 only: Get TStrings.StrictDelimiter also via the debugger/compilers evaluation feature (this is the fix for QC 81604)
  • Get TStrings.DelimitedText also via the debugger/compilers evaluation feature

Either the debugger or the compiler do have a limit of about 4.000 chars for the evaluation result and that is not because of the size of the array (4096 Chars) for the evaluation result in TStringListViewerFrame.Evaluate. Due this limit the built in visualizer cannot show the complete content when it is greater than 4.000 chars.

Furthermore the evaluation calls are slow. Barry Kelly wrote this in an answer on Stack Overflow:

That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results – and this involves pausing and resuming the debugee.

I can only suggest trying to pack more work into the Evaluate call.

For my test in QC 84520 I do see 33 evaluations per second on my 2.1 GHz first generation Core2. For the 64-bit debugger I do see about 120 evaluations per second. For the OS X target my test does somehow not work correctly and I have to report that in QC.

BTW, I do use the following app for testing. What is the last line index in the visualizer on the “SL.Free” line? Is it lesser than 100 or is it 1001?

program SLApp;
 
{$APPTYPE CONSOLE}
 
uses
  Classes;
 
var
  SL: TStringList;
  C: Integer;
begin
  SL := TStringList.Create;
  try
    SL.Add('SELECT * FROM FOO');
    SL.Add('WHERE BAR = 1');
    for C := 0 to 999 do
      SL.Add(StringOfChar(Chr(Ord('A') + C mod 6), 50));
  finally
    SL.Free;
  end;
end.

The solution

The debugger API provides a function to read the memory of the debuggee and the function for that is IOTAProcess.ReadProcessMemory. The (Windows) OS function ReadProcessMemory would not help you, because it does not work for remote debugging and for OS X. IOTAProcess.ReadProcessMemory is much faster than evaluation and I do just read with it all the required data from the TStringList instance. I do not try to describe it – I just show you the important part of the source

CurProcess.ReadProcessMemory(BaseAddr + $30, 4, Result);
if Result > 0 then
begin
  CurProcess.ReadProcessMemory(BaseAddr + $2C, 4, ItemAddr);
  for I := 0 to Result - 1 do
  begin
    CurProcess.ReadProcessMemory(ItemAddr + I * 8, 4, StringAddr);
    CurProcess.ReadProcessMemory(StringAddr - 12, SizeOf(StringRec), StringRec);
    SetLength(ItemStr, StringRec.length);
    CurProcess.ReadProcessMemory(StringAddr, StringRec.length * 2, ItemStr[1]);
    AText := AText + ItemStr + sLineBreak;
  end;
end;

This is the version for Win32 and OSX32. The Win64 version is very much the same except the addresses and amount of bytes to read for pointers. Well this approach has the disadvantage that it may need an adjustment for future RTL versions, but it is worth to do it because of the better performance.

More?

So far I did add two additional features to my visualizer (okay technically it are two visualizers). One thing is a Copy function as requested in QC 88676. Another thing is a replacement visualizer for TStrings that shows something better then just “()” for a TStrings Instance. The following screenshots should show you what I mean.


TStringList in Watch List and Tool tip without a replacement visualizer (Default)


TStringList in Watch List and Tool tip with replacement visualizer

That does work for XE and XE2, but not for 2010, because it is affected by the restriction that you can only have a replacement visualizer or an external viewer. This is fixed since XE and is QC 82368. BTW, when you look at the screenshot in this QC report you will notice that I have replaced the line breaks by spaces. That is because the screenshot in the QC report was a fake and line breaks are only partly supported by the IDE right now.

Installation

Sorry, no setup, but these steps should get it into your IDE.

  • Download the visualizer packages zip file and extract it
  • Start the IDE
  • Add the package with “Component | Install Packages…” that belongs to your version of Delphi
    Delphi 2010 = StrVisualizerPackage140.bpl
    Delphi XE = StrVisualizerPackage150.bpl
    Delphi XE2 = StrVisualizerPackage160.bpl
  • Check in the environment options (Debugger Options | Visualizers) that it looks like this
    [ ] TStrings Visualizer for Delphi
    [x] TStrings Visualizer Classic for Delphi
    [x] Strings Value Replace Visualizer
  • Restart the IDE because for some reason the visualizer with the viewer is not available

Do not ask me what my revised visualizer is named “…Classic…”. I do not know anymore. I have written/modified the visualizer about a year ago and just made a few changes right now.

Warning: The Delphi 2010 version has not been tested! I have just compiled it.

This entry was posted in IDE, Open Tools API. Bookmark the permalink.

2 Responses to TStringList Visualizer revised (2010, XE, XE2)

  1. Markus Ja says:

    Great job!
    It would be also cool to have a visualizer for the TDataSet component :)

    • Uwe Schuster says:

      I guess everyone would like to have a visualizer for TDataSet, but a complete and fast one is hardly possible due the abstraction, complexity and side effects.

      I have an idea for a very limited visualizer that gets the SQL statement and plays with that, but for now I don’t have time to look into it.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">