Jump to content
dummzeuch

Simplified Debug Visualizers for all my TNullableTypes

Recommended Posts

After playing around with debug visualizers, I found that there is a very simple way to provide a visualiser for many record types: Just add a special method (I called it Dump) to those records that returns a string that you can then evaluate in the debugger and display in the debug windows.
There are two caveats though:

 

read on in the blog post...

Share this post


Link to post
Posted (edited)

I do it this way:

 

function TDSH3Visualizer.GetReplacementValue(const Expression, TypeName, EvalResult: string): string;

begin

...

 if MatchText(TypeName, ['TFInteger', 'TFLargeInt', 'TFBit']) then
     DefExpr := Expression + '.AsInteger';
...

Eval(DefExpr, '', Result);
end;

 

Edit:

you can also Eval() private fields like FIsNull FIsUndefind etc. very fast

methods are slower, exceptions are very slow in the Eval()

 

 

function TXYZVisualizer.Eval(const Expr: string; const FormatSpecifiers: string; var EvalResult: string): Boolean;
var
  Done: Boolean;
  ResultStr: array [0 .. 255] of Char;
  ResultAddr, ResultSize, ResultVal: LongWord;
  EvalRes: TOTAEvaluateResult;
  CanModify: Boolean;
begin
  Result := False;
  EvalResult := 'XYZ Visualizer error';
  repeat
    Done := True;
    EvalRes := FCurThread.Evaluate(Expr, @ResultStr, Length(ResultStr), CanModify, eseAll, PAnsiChar(AnsiString(FormatSpecifiers)), ResultAddr, ResultSize, ResultVal, '', 0);
    case EvalRes of
      TOTAEvaluateResult.erOK:
        begin
          EvalResult := ResultStr;
          Result := True;
        end;
      TOTAEvaluateResult.erDeferred:
        begin
          FCompleted := False;
          FDeferredResult := '';
          FNotifierIndex := FCurThread.AddNotifier(Self);
          try
            while not FCompleted do
              FDebugSvcs.ProcessDebugEvents;
          finally
            FCurThread.RemoveNotifier(FNotifierIndex);
          end;
          FNotifierIndex := -1;
          if FDeferredResult <> '' then
            EvalResult := FDeferredResult;
          Result := True;
        end;
      TOTAEvaluateResult.erBusy:
        begin
          FDebugSvcs.ProcessDebugEvents;
          Done := False;
        end;
      TOTAEvaluateResult.erError:
        begin
          Result := False;
        end;
    end;
  until Done;
end;

 

Edited by Attila Kovacs

Share this post


Link to post
17 hours ago, dummzeuch said:

After playing around with debug visualizers, I found that there is a very simple way to provide a visualiser for many record types: Just add a special method (I called it Dump) to those records that returns a string that you can then evaluate in the debugger and display in the debug windows.
There are two caveats though:

 

read on in the blog post...

Can't you make a read-only property out of it instead of a plain method? That way you could make it show up in the watch list of the debugger.

 

 

 

Share this post


Link to post
51 minutes ago, A.M. Hoornweg said:

Can't you make a read-only property out of it instead of a plain method? That way you could make it show up in the watch list of the debugger.

The same would work with explicitly adding the method call to the watch window and allowing side effects. But the point is to show the values in all debugger windows and hints not just in the watch window.

You just gave me an additional idea though: The display value could be cached in a string field which gets updated when the record's value changes, and the visualizer would then just read the field. This would be much faster in the visualizer as no code in the debugged program needs to be executed. But it could significantly slow down the executable in debug mode, depending on how often the value changes.

Share this post


Link to post

I feel you got a typo in your code, @dummzeuch 

	type
	  TNullableDateTime = record
	  private
	    FIsValid: INullableTypesFlagInterface;
	    FValue: TDateTime;
	  public
	    function Dump: string;
	    // other methods and operators
	  end;
	 
	// ...
	implementation
	// ...
	 
	function TNullableDateTime.Dump: string;
	begin
	  if IsValid then
	    Result := DateTimeToStr(Value)  // <- should be FValue?
	  else
	    Result := '<invalid>';
	end;

Should the Dump function not refer to FValue?

Share this post


Link to post
7 minutes ago, Sherlock said:

I feel you got a typo in your code, @dummzeuch 


	type
	  TNullableDateTime = record
	  private
	    FIsValid: INullableTypesFlagInterface;
	    FValue: TDateTime;
	  public
	    function Dump: string;
	    // other methods and operators
	  end;
	 
	// ...
	implementation
	// ...
	 
	function TNullableDateTime.Dump: string;
	begin
	  if IsValid then
	    Result := DateTimeToStr(Value)  // <- should be FValue?
	  else
	    Result := '<invalid>';
	end;

Should the Dump function not refer to FValue?

Value is a property which I have left out in the shortened type declaration. The same goes for the function IsValid, that checks if the field FIsValid is assigned. So, strictly speaking you are right, the example code won't compile. But the point was to show how to implement a Dump method in principle.

If you want to have a look at the actual imlementation of TNullableDateTime, see the unit u_dzNullableDateTime in the dzlib svn repositiory on SourceForge.

Share this post


Link to post

D'uh! Should have figured that one out by myself. Sorry about that. Carry on.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×