Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2023-12

Read Field names from VMT

15 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Unknown author

Hi,

  I want to retrieve the name of the fields in a record/class, at run time. It looks like "TVmt.vFieldTable" is what I need. But I cannot find any documentation about how to explore the content of this table. I appreciate any pointer.

Thank you,
Amir

Sven Barth
to Unknown author

Amir--- via fpc-pascal schrieb am Di.,
26. Dez. 2023, 07:03:

Show quoted text 7 lines
Hi, I want to retrieve the name of the fields in a record/class, at run time. It looks like "TVmt.vFieldTable" is what I need. But I cannot find any documentation about how to explore the content of this table. I appreciate any pointer.

This only works for published fields and only fields of type class or interface can be published.

Anything more requires extended RTTI, which is not yet implemented in FPC (it's already more or less ready in a merge request, but there's still the one or other issue that needs to be addressed).

Regards,
Sven

Unknown author
to Sven Barth
Sven Barth wrote 15 lines
Amir--- via fpc-pascal schrieb am Di., 26. Dez. 2023, 07:03: Hi,    I want to retrieve the name of the fields in a record/class, at run time. It looks like "TVmt.vFieldTable" is what I need. But I cannot find any documentation about how to explore the content of this table. I appreciate any pointer. This only works for published fields and only fields of type class or interface can be published.

That would work for me. How can I enumerate over those fields?

Best,
Amir

Sven Barth
to Unknown author
Amir--- wrote 18 lines
On 12/26/23 01:14, Sven Barth via fpc-pascal wrote: > Amir--- via fpc-pascal schrieb am > Di., 26. Dez. 2023, 07:03: > > Hi, > >    I want to retrieve the name of the fields in a record/class, > at run > time. It looks like "TVmt.vFieldTable" is what I need. But I > cannot find > any documentation about how to explore the content of this table. I > appreciate any pointer. > > > This only works for published fields and only fields of type class or > interface can be published. That would work for me. How can I enumerate over those fields?

You can use the PVmtFieldTable and PVmtFieldEntry types from the TypInfo unit:

=== code begin ===

program tfield;
{$mode objfpc}{$H+}
uses
   TypInfo;

type
  {$M+}
  TSub = class

   end;
   TTest = class
   published
     fTest: TSub;
   end;
var
   vft: PVmtFieldTable;
   vfe: PVmtFieldEntry;
   i: SizeInt;
begin
   vft := PVmtFieldTable(PVMT(TTest)^.vFieldTable);
   Writeln(vft^.Count, ' field(s) with ', vft^.ClassTab^.Count, ' type(s)');
   for i := 0 to vft^.Count - 1 do begin
     vfe := vft^.Field[i];
     Writeln(i, ' -> ', vfe^.Name, ' @ ', vfe^.FieldOffset, ' of type ',
vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^.ClassName);
   end;
end.

=== code end ===

=== output begin ===

PS C:\fpc\git> .\testoutput\tfield.exe
1 field(s) with 1 type(s)
0 -> fTest @ 8 of type TSub

=== output end ===

Side note: contrary to what I had originally written only classes, but not interfaces are allowed for published fields and they need to have $M enabled.

Regards,
Sven

Unknown author
to Sven Barth: You can use the PVmtFieldTable and PVmtFieldEntry types from the TypInfo unit: === code…

How can I set the value?
I tried something like

Test := TTest.Create
Ptr := Pointer(Test);
TSub(Ptr+8) := TSub.Create;

But it is not working?

Show quoted text 58 lines
You can use the PVmtFieldTable and PVmtFieldEntry types from the TypInfo unit: === code begin === program tfield; {$mode objfpc}{$H+} uses   TypInfo; type   {$M+}   TSub = class   end;   TTest = class   published     fTest: TSub;   end; var   vft: PVmtFieldTable;   vfe: PVmtFieldEntry;   i: SizeInt; begin   vft := PVmtFieldTable(PVMT(TTest)^.vFieldTable);   Writeln(vft^.Count, ' field(s) with ', vft^.ClassTab^.Count, ' type(s)');   for i := 0 to vft^.Count - 1 do begin     vfe := vft^.Field[i];     Writeln(i, ' -> ', vfe^.Name, ' @ ', vfe^.FieldOffset, ' of type ', vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^.ClassName);   end; end. === code end === === output begin === PS C:\fpc\git> .\testoutput\tfield.exe 1 field(s) with 1 type(s) 0 -> fTest @ 8 of type TSub === output end === Side note: contrary to what I had originally written only classes, but not interfaces are allowed for published fields and they need to have $M enabled. Regards, Sven _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Michael Van Canneyt
to Unknown author
Amir--- wrote 9 lines
How can I set the value? I tried something like Test := TTest.Create Ptr := Pointer(Test); TSub(Ptr+8) := TSub.Create; But it is not working?

Depending on your platform, it can be an alignment issue.

Use

  function TObject.FieldAddress(const name : shortstring) : pointer;

This function is used internally by the streaming system (see TComponent.SetReference), so it should always work.

Michael.

Show quoted text 63 lines
> You can use the PVmtFieldTable and PVmtFieldEntry types from the > TypInfo unit: > > === code begin === > > program tfield; > > {$mode objfpc}{$H+} > > uses >   TypInfo; > > type >   {$M+} >   TSub = class > >   end; > >   TTest = class >   published >     fTest: TSub; >   end; > > var >   vft: PVmtFieldTable; >   vfe: PVmtFieldEntry; >   i: SizeInt; > begin >   vft := PVmtFieldTable(PVMT(TTest)^.vFieldTable); >   Writeln(vft^.Count, ' field(s) with ', vft^.ClassTab^.Count, ' > type(s)'); >   for i := 0 to vft^.Count - 1 do begin >     vfe := vft^.Field[i]; >     Writeln(i, ' -> ', vfe^.Name, ' @ ', vfe^.FieldOffset, ' of type > ', vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^.ClassName); >   end; > end. > > === code end === > > === output begin === > > PS C:\fpc\git> .\testoutput\tfield.exe > 1 field(s) with 1 type(s) > 0 -> fTest @ 8 of type TSub > > === output end === > > Side note: contrary to what I had originally written only classes, but > not interfaces are allowed for published fields and they need to have > $M enabled. > > Regards, > Sven > > _______________________________________________ > fpc-pascal maillist - > https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Unknown author
to Michael Van Canneyt
   Params := TParams.Create;
   WriteLn(Params.Int2.IntVal);
   WriteLn(TInteger(Params.FieldAddress('Int2')).IntVal);

The third line does not work (the zipped code is attached).

Michael Van Canneyt wrote 92 lines
On Sun, 21 Jan 2024, Amir--- via fpc-pascal wrote: > How can I set the value? > I tried something like > > Test := TTest.Create > > Ptr := Pointer(Test); > TSub(Ptr+8) := TSub.Create; > > But it is not working? Depending on your platform, it can be an alignment issue. Use  function TObject.FieldAddress(const name : shortstring) : pointer; This function is used internally by the streaming system (see TComponent.SetReference), so it should always work. Michael. >> You can use the PVmtFieldTable and PVmtFieldEntry types from the >> TypInfo unit: >> >> === code begin === >> >> program tfield; >> >> {$mode objfpc}{$H+} >> >> uses >>   TypInfo; >> >> type >>   {$M+} >>   TSub = class >> >>   end; >> >>   TTest = class >>   published >>     fTest: TSub; >>   end; >> >> var >>   vft: PVmtFieldTable; >>   vfe: PVmtFieldEntry; >>   i: SizeInt; >> begin >>   vft := PVmtFieldTable(PVMT(TTest)^.vFieldTable); >>   Writeln(vft^.Count, ' field(s) with ', vft^.ClassTab^.Count, ' >> type(s)'); >>   for i := 0 to vft^.Count - 1 do begin >>     vfe := vft^.Field[i]; >>     Writeln(i, ' -> ', vfe^.Name, ' @ ', vfe^.FieldOffset, ' of type >> ', vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^.ClassName); >>   end; >> end. >> >> === code end === >> >> === output begin === >> >> PS C:\fpc\git> .\testoutput\tfield.exe >> 1 field(s) with 1 type(s) >> 0 -> fTest @ 8 of type TSub >> >> === output end === >> >> Side note: contrary to what I had originally written only classes, >> but not interfaces are allowed for published fields and they need to >> have $M enabled. >> >> Regards, >> Sven >> >> _______________________________________________ >> fpc-pascal maillist  -  >> https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal > > _______________________________________________ > fpc-pascal maillist  -  > https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal > _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Michael Van Canneyt
to Unknown author
Amir--- wrote 5 lines
  Params := TParams.Create;   WriteLn(Params.Int2.IntVal);   WriteLn(TInteger(Params.FieldAddress('Int2')).IntVal); The third line does not work (the zipped code is attached).

It should be

WriteLn(TInteger(Params.FieldAddress('Int2')^).IntVal);

Terribly convoluted code, though...

Michael.

Unknown author
to Michael Van Canneyt
Michael Van Canneyt wrote 13 lines
On Mon, 22 Jan 2024, Amir--- via fpc-pascal wrote: >   Params := TParams.Create; >   WriteLn(Params.Int2.IntVal); >   WriteLn(TInteger(Params.FieldAddress('Int2')).IntVal); > > The third line does not work (the zipped code is attached). It should be WriteLn(TInteger(Params.FieldAddress('Int2')^).IntVal);

+1

Unknown author
to Michael Van Canneyt: It should be WriteLn(TInteger(Params.FieldAddress('Int2')^).IntVal); Terribly convoluted…

I am still struggling with this!

for i := 0 to vft^.Count - 1 do
begin
        vfe := vft^.Field[i];
        Name := vfe^.Name;
        ChildTClass := vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^;
        ChildObj := ChildTClass.Create;
        TObject(Obj.FieldAddress(Name)^) := ChildObj;
end;

Looks like "ChildTClass.ClassName" is correct but the constructor of the appropriate class is not being called.

Michael Van Canneyt wrote 21 lines
On Mon, 22 Jan 2024, Amir--- via fpc-pascal wrote: >   Params := TParams.Create; >   WriteLn(Params.Int2.IntVal); >   WriteLn(TInteger(Params.FieldAddress('Int2')).IntVal); > > The third line does not work (the zipped code is attached). It should be WriteLn(TInteger(Params.FieldAddress('Int2')^).IntVal); Terribly convoluted code, though... Michael. _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Michael Van Canneyt
to Unknown author
Amir--- wrote 14 lines
I am still struggling with this! for i := 0 to vft^.Count - 1 do begin        vfe := vft^.Field[i];        Name := vfe^.Name;        ChildTClass := vft^.ClassTab^.ClassRef[vfe^.TypeIndex - 1]^;        ChildObj := ChildTClass.Create;        TObject(Obj.FieldAddress(Name)^) := ChildObj; end; Looks like "ChildTClass.ClassName" is correct but the constructor of the appropriate class is not being called.

Without more info (declaration of ChildTClass, declaration of the constructor of that class etc), it is not possible to comment.

We need a complete compilable code sample to provide you with more insight.

Michael.

Unknown author
to Michael Van Canneyt
Show quoted text 7 lines
Without more info (declaration of ChildTClass, declaration of the constructor of that class etc), it is not possible to comment. We need a complete compilable code sample to provide you with more insight.

Please have a look at the attachment.

Best,
Amir

Michael Van Canneyt
to Unknown author
Amir--- wrote 8 lines
> Without more info (declaration of ChildTClass, declaration of the > constructor > of that class etc), it is not possible to comment. > > We need a complete compilable code sample to provide you with more insight. > Please have a look at the attachment.

The constructor of TObject is not virtual, in difference with the destructor.

Given the definitions

   ChildObj: TObject;
   ChildTClass: TClass;

The following statement

   ChildObj := ChildTClass.Create;

will always use the TObject.Create, since it is not virtual.

In general, you cannot use TObject for this kind of thing.

For this to work, you need to create a descendent of TObject with a virtual constructor (call it TMyObject) of known signature, and do

   ChildObj: TMyObject;
   ChildTClass: TMyClass; // class of TMyobject

then

   ChildObj := ChildTClass.Create;

will take the correct overridden constructor.

Your code is also incomplete in the sense that it will only work for classes with a constructor without arguments.
If a overloaded constructor exists which takes arguments (like TComponent), it will not be called and the class will not be instantiated correctly. But maybe that will not be your use-case.

I attached a version of your program that works and has no memleaks.

Michael.

Unknown author
to Michael Van Canneyt

I see... The trick was to define TMyClass!

Thanks!

Michael Van Canneyt wrote 57 lines
On Wed, 31 Jan 2024, Amir--- via fpc-pascal wrote: > >> Without more info (declaration of ChildTClass, declaration of the >> constructor >> of that class etc), it is not possible to comment. >> >> We need a complete compilable code sample to provide you with more >> insight. >> > Please have a look at the attachment. The constructor of TObject is not virtual, in difference with the destructor. Given the definitions   ChildObj: TObject;   ChildTClass: TClass; The following statement   ChildObj := ChildTClass.Create; will always use the TObject.Create, since it is not virtual. In general, you cannot use TObject for this kind of thing. For this to work, you need to create a descendent of TObject with a virtual constructor (call it TMyObject) of known signature, and do   ChildObj: TMyObject;   ChildTClass: TMyClass; // class of TMyobject then   ChildObj := ChildTClass.Create; will take the correct overridden constructor. Your code is also incomplete in the sense that it will only work for classes with a constructor without arguments. If a overloaded constructor exists which takes arguments (like TComponent), it will not be called and the class will not be instantiated correctly. But maybe that will not be your use-case. I attached a version of your program that works and has no memleaks. Michael. _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal