5

I have a working debugger visualizer that helps visualize variables of type TIntTime.

TIntTime = type integer;

The visualizer replaces a number of seconds since midnight with a time string HH:MM:SS. This works fine on variables of type TIntTime during a debug session, but not on functions. For example if I place GetiTime in a watch

function GetiTime: TIntTime;
begin
  Result:=30000;
end;

the watch will show 30000. The expected replaced value is '08:20:00'. The visualizer does not intercept function return values of type TIntTime and this is the problem.

I am using Delphi 10 Seattle. My visualizer is based on DateTimeVisualizer.pas found in Delphi 10\source\Visualizers. The DateTimeVisualizer suggests that function return values are intercepted by using the type name string 'function: TIntTime' in GetSupportedType. I have tried

'function: TIntTime'
'function:TIntTime'
'function::TIntTime'

without luck. I suspect it is a question of getting this type name string correct, but haven't been able to find information about the formatting on the internet.

If I instead place GetDateTime in a watch it shows '14-02-2018 13:20:30' as expected. If I switch the TDateTime/TDate/TTime visualizer off in options the watch shows 43145.5559... This tells me that it is possible to intercept function return values with a visualizer.

  function GetDateTime: TDateTime;
  begin
    Result:=EncodeDateTime(2018,2,14,13,20,30,0);
  end;

In my case it is not an option to use the TDateTime data type. So my question is: how can I get my visualizer to intercept function return values of type TIntTime?

Below is the source for the TIntTime visualizer

unit IntTimeVisualizer;

interface

procedure Register;

implementation

uses
  Classes, Forms, SysUtils, ToolsAPI;

resourcestring
  sIntTimeVisualizerName = 'TIntTime Visualizer for Delphi';
  sIntTimeVisualizerDescription = 'Displays TIntTime instances in a human-readable time format rather than as an integer value';

type
  TDebuggerIntTimeVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer,
    IOTADebuggerVisualizerValueReplacer, IOTAThreadNotifier, IOTAThreadNotifier160)
  private
    FCompleted: Boolean;
    FDeferredResult: string;
  public
    { IOTADebuggerVisualizer }
    function GetSupportedTypeCount: Integer;
    procedure GetSupportedType(Index: Integer; var TypeName: string;
      var AllDescendants: Boolean);
    function GetVisualizerIdentifier: string;
    function GetVisualizerName: string;
    function GetVisualizerDescription: string;
    { IOTADebuggerVisualizerValueReplacer }
    function GetReplacementValue(const Expression, TypeName, EvalResult: string): string;
    { IOTAThreadNotifier }
    procedure EvaluteComplete(const ExprStr: string; const ResultStr: string;
      CanModify: Boolean; ResultAddress: Cardinal; ResultSize: Cardinal;
      ReturnCode: Integer);
    procedure ModifyComplete(const ExprStr: string; const ResultStr: string;
      ReturnCode: Integer);
    procedure ThreadNotify(Reason: TOTANotifyReason);
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
    { IOTAThreadNotifier160 }
    procedure EvaluateComplete(const ExprStr: string; const ResultStr: string;
      CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
      ReturnCode: Integer);
  end;

  TIntTimeType = (dttIntTime);

  TIntTimeVisualizerType = record
    TypeName: string;
    TimeType: TIntTimeType;
  end;

const
  IntTimeVisualizerTypes: array[0..1] of TIntTimeVisualizerType =
  (
    (TypeName: 'TIntTime'; TimeType: dttIntTime;),    //<-- This type is working fine
    (TypeName: 'function: TIntTime'; TimeType: dttIntTime;)  //<-- This type is not working
  );

{ TDebuggerIntTimeVisualizer }

procedure TDebuggerIntTimeVisualizer.AfterSave;
begin
  // don't care about this notification
end;

procedure TDebuggerIntTimeVisualizer.BeforeSave;
begin
  // don't care about this notification
end;

procedure TDebuggerIntTimeVisualizer.Destroyed;
begin
  // don't care about this notification
end;

procedure TDebuggerIntTimeVisualizer.Modified;
begin
  // don't care about this notification
end;

procedure TDebuggerIntTimeVisualizer.ModifyComplete(const ExprStr,
  ResultStr: string; ReturnCode: Integer);
begin
  // don't care about this notification
end;

procedure TDebuggerIntTimeVisualizer.EvaluteComplete(const ExprStr,
  ResultStr: string; CanModify: Boolean; ResultAddress, ResultSize: Cardinal;
  ReturnCode: Integer);
begin
  EvaluateComplete(ExprStr, ResultStr, CanModify, TOTAAddress(ResultAddress),
    LongWord(ResultSize), ReturnCode);
end;

procedure TDebuggerIntTimeVisualizer.EvaluateComplete(const ExprStr,
  ResultStr: string; CanModify: Boolean; ResultAddress: TOTAAddress; ResultSize: LongWord;
  ReturnCode: Integer);
begin
  FCompleted := True;
  if ReturnCode = 0 then
    FDeferredResult := ResultStr;
end;

procedure TDebuggerIntTimeVisualizer.ThreadNotify(Reason: TOTANotifyReason);
begin
  // don't care about this notification
end;

function TDebuggerIntTimeVisualizer.GetReplacementValue(
  const Expression, TypeName, EvalResult: string): string;
var
  TimeType: TIntTimeType;
  I: Integer;

  function IntTimeToStr(s: Integer): string;
  var
    hh, mm, ss: integer;
  begin
    hh:=s div 3600;
    mm:=(s div 60)-hh*60;
    ss:=s mod 60;
    Result:=Format('%.2d:%.2d:%.2d',[hh,mm,ss]);
  end;

  function FormatResult(const LEvalResult: string; DTType: TIntTimeType; out ResStr: string): Boolean;
  var
    IntValue: integer;
  begin
    Result := True;
    try
      if not TryStrToInt(LEvalResult, IntValue) then
        Result:=false
      else
        case DTType of
          dttIntTime: ResStr:=IntTimeToStr(IntValue);
        end;
    except
      Result := False;
    end;
  end;

begin
  TimeType := TIntTimeType(-1);
  for I := Low(IntTimeVisualizerTypes) to High(IntTimeVisualizerTypes) do begin
    if TypeName = IntTimeVisualizerTypes[I].TypeName then begin
      TimeType:=IntTimeVisualizerTypes[I].TimeType;
      Break;
    end;
  end;

  if not FormatResult(EvalResult, TimeType, Result) then
    Result := EvalResult;
end;

function TDebuggerIntTimeVisualizer.GetSupportedTypeCount: Integer;
begin
  Result := Length(IntTimeVisualizerTypes);
end;

procedure TDebuggerIntTimeVisualizer.GetSupportedType(Index: Integer; var TypeName: string;
  var AllDescendants: Boolean);
begin
  AllDescendants := false;
  TypeName := IntTimeVisualizerTypes[Index].TypeName;
end;

function TDebuggerIntTimeVisualizer.GetVisualizerDescription: string;
begin
  Result := sIntTimeVisualizerDescription;
end;

function TDebuggerIntTimeVisualizer.GetVisualizerIdentifier: string;
begin
  Result := ClassName;
end;

function TDebuggerIntTimeVisualizer.GetVisualizerName: string;
begin
  Result := sIntTimeVisualizerName;
end;

var
  IntTimeVis: IOTADebuggerVisualizer;

procedure Register;
begin
  IntTimeVis:=TDebuggerIntTimeVisualizer.Create;
  (BorlandIDEServices as IOTADebuggerServices).RegisterDebugVisualizer(IntTimeVis);
end;

procedure RemoveVisualizer;
var
  DebuggerServices: IOTADebuggerServices;
begin
  if Supports(BorlandIDEServices, IOTADebuggerServices, DebuggerServices) then begin
    DebuggerServices.UnregisterDebugVisualizer(IntTimeVis);
    IntTimeVis:=nil;
  end;
end;

initialization

finalization
  RemoveVisualizer;
end.
Jan Lauridsen
  • 586
  • 4
  • 9
  • Why is your visualizer defining its own `TIntTime` type? That is going to be different than other `TIntTime` types used by your apps. If you want to share a single type, you will have to put it into a package that can be shared by the visualizer and apps. – Remy Lebeau Feb 14 '18 at 17:53
  • Hi Remy, thanks for the comment. Yes, you are correct. I'll remove it from the question. – Jan Lauridsen Feb 14 '18 at 17:58
  • It works for me on Delphi 10.2.2, so it maybe just a bug in Delphi 10 Seattle – EugeneK Feb 14 '18 at 21:28
  • Thanks Eugene for testing this. In that case I can stop trying to get it to work on Seattle :-) – Jan Lauridsen Feb 14 '18 at 23:53
  • When I was writing a visualizer for my BigInteger and BigDecimal types, I simply looked at the strings I got. Perhaps you can do that too? Then you can see how and what you must parse to get the right format and values. – Rudy Velthuis Feb 15 '18 at 08:06
  • Hi Rudy, the GetReplacementValue function or any of the other interface methods are not called for TIntTime function return value, so there is nothing to look at. – Jan Lauridsen Feb 15 '18 at 10:27
  • `function: TIntTime` this is type of function why you expect that ide will be execute visualizer for you **TIntType**? Have you debug you code? – Vasek Feb 16 '18 at 07:42
  • Hi Vasek, the TIntTime visualizer is based on the TDateTime visualizer found in Delphi 10 source directory. The TDateTime visualizer registers 'TDateTime' and 'function: TDateTime' in GetSupportedType. If I remove 'function: TDateTime' from the TDateTime visualizer then the debugger will no longer display TDateTime function return values as a datetime string. I use CodeSite for checking what is going on inside the visualizer. – Jan Lauridsen Feb 16 '18 at 08:47
  • I test you code in XE6 and them work as you expected. It looks like this is really a bug in Delphi 10 Seattle. Try register standalone visualizer for `function: TIntTime`, maybe it help. – Vasek Feb 18 '18 at 20:28

0 Answers0