How to Get a value of a property at runtime via reflection?


(SH) #1

As the title Shows, am i able in Oxygene Island, to retrieve a value of, for instance, a class, or record?

typeOf(T).Properties[0].Value //i know, wrong Syntax, but like such, i am missing currently, because the propertyinfo variable has no "value" property, in C# there was a "value" property

(Carlo Kok) #2

This isn’t supported yet.

(SH) #3

ok, do you plan it, to Support it on the Island platform?

(Carlo Kok) #4

Yes, there are plans.

(SH) #5

Ok, could i get a value of a property maybe on a different way?


Pascal/FPC had a way, which was the DFM, where you could create a dictionary and fill the values of the needed class in the dictionary and then populate it.

Does oxygene provide a Thing like that?

(Carlo Kok) #6

Not yet. As I said, we’re planning this feature, but it’s not ready yet. For simple properties (no parameters) and simple types, you could use reflection to get the method pointer (For the read accessor of a property). Then cast that to a function pointer like this:

  ObjectGetterMethod = function(aSelf: Object): Object;
  IntegerGetterMethod = function(aSelf: Object): Integer;

if it's an integer:
IntegerGetterMethod(ProcPtr)(aInstance) will give you the result

if it's an Object:
ObjectGetterMethod(ProcPtr)(aInstance) will give you the result


(SH) #7

First, thanks for the suggestion :slight_smile:

But what exactly do u mean by: simple properties, with no Parameters?^^

But isnt integer not an object?

(Carlo Kok) #8

No index properties.

You need a different function pointer for every distinct value type result.

(SH) #9

Hmm :confused:

Unfortunately, this dont work, at least in my example…

  var text := 'hello world';
  var proc_ptr := text.GetType().Properties.FirstOrDefault().ReadMethod;
  var getter: Read_Method := Read_Method(proc_ptr);
  getter(text.Length);  //i also tried getter(text) and this didnt work as well

P.s The aim of this example is to get the “LENGTH” Property^^

(Carlo Kok) #10

This works on any custom class (really only String didn’t have proper rrti for this), next build the route through Properties works too:

namespace ConsoleApplication401;

  Read_Method = public function(aSelf: Object): Integer;
  MyString = public class 
    fs: String;
    constructor(s: string); begin fs := s; end;
    property Length: Integer read fs.Length;
  Program = class
    class method Main(args: array of String): Int32;
      var text := new MyString('hello world');
      var proc_ptr := text.GetType().Methods.FirstOrDefault(a -> a.Name ='get_Length');
      var rm: Read_Method := Read_Method(proc_ptr.Pointer);
      // add your own code here
      writeLn('The magic happens here.');


(SH) #11

Thanks for the help carlo :slight_smile:

It works pretty fine :slight_smile:

In Addition to that, how works this with fields??

from records for instance, where i dont have always properties?


(Carlo Kok) #12

you can use the InstanceOffset property of FieldInfo to find the offset of the field relative to the record. then it kind of depends on how you have it. Say you have it boxed in an Object, it would be something along the lines:

^Integer(InternalCalls.Cast(myobject) + fieldInfo.InstanceOffset + if acontainingtype.IsValueType then sizeof(Pointer) else 0)

(to get an integer field)

(SH) #13

Ok, that seams more ugly :smiley: :smiley:

But, honestly, i dont understand the line: "if acontainingtype.IsValueType then sizeof(Pointer) else 0) " because i dont know where the “acontainingtype.IsValueType” and the “Pointer” came from^^

(Carlo Kok) #14

sizeof(Pointer) should have been sizeof(^Void) (which is either 4 or 8)

what’s happening is this, InstanceOffset always gives the offset of an instance field as it would be stored in memory. For classes there’s always the Vmt first, then the first field, then the second etc. This is taken in account, for value types there is no vmt at all, so the first field has instance offset 0. However a function that would return a field from a struct would probably take an Object parameter, which means it has a boxed struct; those DO have a vmt prepended. so you have to add sizeof(^void) to it.

(SH) #15


Works pretty fine :slight_smile:

But the last Thing i dont really get is, why do i Need the “sizeof(^Void)” when i know for 100% it is an pure record/valuetype and it is NOT boxed?

I tested it before without that line and the Output was: “?” and when i added it, the Output of my record was fine, have a look:

  var data : TSomeData;
  data.A := 'D';      
  data.B := '2';
  data.C := '3';
  data.D := '4';
  data.E := 177;

  var fieldInfo := data.GetType().Fields.FirstOrDefault();      

  var value := ^Char(InternalCalls.Cast(data) + fieldInfo.InstanceOffset + if data.GetType().IsValueType then sizeof(^Void) else 0); 
   // add your own code here

(Carlo Kok) #16

In this case @data + instancefieldoffset will do

(SH) #17

and what would be the underlying result type of it?

I ask this because the Compiler say: "Cannot cast “method pointer” to object.

(Carlo Kok) #18

^String(@data + fieldInfo.InstanceOffset)^ or something like it (depends on the types of Tsomedata?)

(SH) #19

Hmm, can u have a look :confused: ?

 var rec : TRec;
  rec.X := 10;
  rec.Y := 20;
  rec.Z := 30;

  var d := ^Int32(@rec + rec.GetType().Fields.FirstOrDefault().InstanceOffset)^; // Error: "Cannot cast from method pointer to ^int32"

(Carlo Kok) #20

This should do:

namespace ConsoleApplication5;

TRec = record public X, Y, Z: Integer; end;
  Program = class
    class method Main(args: array of String): Int32;
      // add your own code here
        var rec : TRec;
        rec.X := 10;
        rec.Y := 20;
        rec.Z := 30;
        for each el in  rec.GetType().Fields do begin 
            writeLn('off: '+el.InstanceOffset+' '+el.Name);
            var d := ^Int32(IntPtr(@rec) + el.InstanceOffset)^; // Error: "Cannot cast from method pointer to ^int32"


but i just noticed InstanceOffset is wrong for the 2nd and 3rd field, so I’ll be taking a look at that.