Debug members of my class kept as interface variable. lazarus / 12-Debugger ---------------------------------------------------------------------- tol · 2024-05-16 19:38 How can I debug members of my class, which is kept as an interface? With "normal" classes everything seems to work: For example, I can see the members of a previously constructed object of type TStringList in the local variables window. But not if I create my own class. The type of the own class type was derived from Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type TTest = class(TInterfacedObject, ISomeInterface) ( And instantiated  via a factory class, just in case if that is relevant.  ) The instance is then stored in a variable of type ISomeInterface. When debugging, I only see the address of the interface in the "local variables" window. None of the members. The same applies to the list of monitored expressions etc. I can't see the members via any debugger window. I can't enter an expression that uses a member of the interface either. ("Cannot get member "X" from non-structured type .. "). How can I debug instances of such classes? ---------------------------------------------------------------------- Martin_fr · 2024-05-17 07:23 Unfortunately there is no way. Currently FPC does not write any debug info for the interface. But even if, the interface does only have methods. it does not have a read-able translation into the object that implements it. (Though that would be up to fpc to implement creating such a translation for the debugger/ at least when possible). Usually an interface has a different implementation for each object that it implements. If you use asm to step into the call to the interface, then you find code like Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 0000000100001660 4883E920                 sub rcx,$200000000100001664 E997FFFFFF               jmp -$00000069    # $0000000100001600 Foo project1.lpr:210000000100001669 0000                     add [rax],al000000010000166B 0000                     add [rax],al000000010000166D 0000                     add [rax],al000000010000166F 005548                   add [rbp+$48],dl  The exact code can vary. Or may for other fpc versions be completely different. The "$20" in the first line is important. But keep in mind that even for the same interface it will vary, depending on the underlaying object that implements that interface. If you know this value you can watch   TObject(pointer(MYInterfacei)-$20) Again, if you run the same code again, but another class is hidden behind the interface then the $20 changes. So you must asm step into the interface each time. ---------------------------------------------------------------------- cdbc · 2024-05-17 09:27 Hi Remember, there's *always* an object behind an interface, implementing it! I use interfaces extensively and what I do is this: 1) for properties, I step into the getter/setter, this lands me right smack in     the implementing object, then I can just hold the mouse over the internal     fields. 2) for functions/procedures it's pretty much the same, step into and then I     usually watch local variables... 3) when debugging I 'Log' a lot to screen and file and I keep 'Logging' an     option via paramstr() in released programs, then I can just start the app     from the command-line and let it tell me what's wrong... edit: Try debugging interfaces kept as plugins in libraries ...then you can talk about *Fiddly* HTH Regards Benny ---------------------------------------------------------------------- Martin_fr · 2024-05-17 17:53 Ok, so now I did take the time... With my version of FPC 3.2.3, fpc/rtl/project all compiled with -O1 Setting the watch to "use instance type" Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TObject(Pointer(MyInterface)-^byte(((^^^byte(MyInterface)^+3)^)+3)^) The expression gets the pointer to a function in the interface (the first + 3 is an offset into the internal data - using pointermath, on 64bit that is 24 bytes), and then adds 3. This is the offset to the "20" that is subtracted (in the asm sample of my last post) in the machine code. So if the interface is using a different value (but the same machine code/ compiled with same optimization settings, etc)  then this will find the underlaying object. ---------------------------------------------------------------------- tol · 2024-05-17 18:07 @Martin_fr Thank you for the time spent on this. That's what I would have expected, that it would run automatically in the background. (In other languages, debugging the object via one of its interfaces is no problem) As a "hi-level" developer, I don't really want to (have to) know the internal implementation of the interfaces.Quote from: Martin_fr on May 17, 2024, 07:53:16 pmOk, so now I did take the time... With my version of FPC 3.2.3, fpc/rtl/project all compiled with -O1 Setting the watch to "use instance type" Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i TObject(Pointer(MyInterface)-^byte(((^^^byte(MyInterface)^+3)^)+3)^) The expression gets the pointer to a function in the interface (the first + 3 is an offset into the internal data - using pointermath, on 64bit that is 24 bytes), and then adds 3. This is the offset to the "20" that is subtracted (in the asm sample of my last post) in the machine code. So if the interface is using a different value (but the same machine code/ compiled with same optimization settings, etc)  then this will find the underlaying object. ---------------------------------------------------------------------- PascalDragon · 2024-05-22 20:08 Quote from: cdbc on May 17, 2024, 11:27:12 amRemember, there's *always* an object behind an interface, implementing it! That is in fact not true. It's possible to construct an interface “instance” without there being an object instance backing it. The Generics.Collections unit uses this for example. ---------------------------------------------------------------------- cdbc · 2024-05-22 20:47 Hi ...Ok Sven I'll bite, have you by any chance, got a short example of this constructing, without an object, business, up your sleeve? You've just peek'ed my curiousity...  Regards Benny ---------------------------------------------------------------------- Martin_fr · 2024-05-22 21:52 Look at the unit he mentioned, and find stuff like "    // IComparer VMT" => constructing the interface without the compiler (if you know the memory layout that the compiler expects....) An interface is (simplified) a jump table (a VMT), and maybe some data. So putting together your own VMT, then it can have any code pointer, as long as the code they point to, matches the method signatures in the interface.  But this "any code" then does not need to go to an object/instance...  I haven't checked, but there may also be code to inject those "interfaces" into classes, so that "SomeClass as IThisInterface" will return such an interface. And then such an interface, despite being returned by that class, has no link to that class. At least I guess that is what he meant. ---------------------------------------------------------------------- PascalDragon · 2024-05-26 20:41 It's as Martin_fr said. I've extracted the important parts here: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program tmanintf; {$mode objfpc} type  TInterface = class  public    function QueryInterface(constref {%H-}IID: TGUID;{%H-} out Obj): HResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual;    function _AddRef: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; abstract;    function _Release: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};  virtual; abstract;  end;   TRawInterface = class(TInterface)  public    function _AddRef: LongInt; override;    function _Release: LongInt; override;  end;   ICompare = interface    function Compare(const ALeft, ARight: Int8): Integer;  end;   PComparerVMT = ^TComparerVMT;  TComparerVMT = packed record    QueryInterface: CodePointer;    _AddRef: CodePointer;    _Release: CodePointer;    Compare: CodePointer;  end;   TCompare = class    class function Int8(const ALeft, ARight: Int8): Integer;  end; const  { please not that while this uses instance methods of TRawInterface this does not necessarily    mean that a TRawInterface instance is passed in; e.g. the code in Generics.Defaults uses    a different type as Self that contains the reference count for those types where the    reference count is necessary }  Comparer_Int8_VMT  : TComparerVMT = (QueryInterface: @TRawInterface.QueryInterface; _AddRef: @TRawInterface._AddRef; _Release: @TRawInterface._Release; Compare: @TCompare.Int8);   Comparer_Int8_Instance  : Pointer = @Comparer_Int8_VMT; function TInterface.QueryInterface(constref IID: TGUID; out Obj): HResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};begin  Result := E_NOINTERFACE;end; function TRawInterface._AddRef: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};begin  Result := -1;end; function TRawInterface._Release: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};begin  Result := -1;end; class function TCompare.Int8(const ALeft, ARight: Int8): Integer;begin  Result := ALeft - ARight;end; var  intf: ICompare;begin  intf := ICompare(@Comparer_Int8_Instance);  Writeln(intf.Compare(2, 4));  Writeln(intf.Compare(4, 2));  Writeln(intf.Compare(4, 4));end. ---------------------------------------------------------------------- cdbc · 2024-05-27 06:27 Hi @PascalDragon & @Martin_fr: Thank you, this is all possible kinds of Cool  It's like getting a new toy  ...now onto playing with it.... Hmmm, the /alternate/ 'Self' would then be the 'Spoof'-thingy in 'Generics.Collections'?!? -> Gotta get my head 'round that  Regards Benny ---------------------------------------------------------------------- Martin_fr · 2024-07-25 19:25 I have added a new intrinsic to FpDebug (Lazarus 3.99) https://wiki.freepascal.org/FpDebug-Watches-Intrinsic- Functions#CC%28instance%29_%7BChildClass%7D This will (try to) work out the object to which an interface forwards its calls. You can then inspect the data on those objects. (Limited to Windows/Linux and Intel-compatible). Add a watch to the watches window: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i :I2O(MyIntfVar) ---------------------------------------------------------------------- cdbc · 2024-07-25 21:30 Hi Cool & useful, Thanks Martin  Regards Benny