dummzeuch 1506 Posted March 24 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
Attila Kovacs 629 Posted March 24 (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 March 24 by Attila Kovacs Share this post Link to post
A.M. Hoornweg 144 Posted March 25 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
dummzeuch 1506 Posted March 25 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
Sherlock 663 Posted March 27 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
dummzeuch 1506 Posted March 27 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
Sherlock 663 Posted March 27 D'uh! Should have figured that one out by myself. Sorry about that. Carry on. Share this post Link to post