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
Ok, could i get a value of a property maybe on a different way?
AFAIK:
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.
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:
type
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
Etc.
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^^
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;
type
Read_Method = public function(aSelf: Object): Integer;
MyString = public class
private
fs: String;
public
constructor(s: string); begin fs := s; end;
property Length: Integer read fs.Length;
end;
Program = class
public
class method Main(args: array of String): Int32;
begin
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);
writeln(rm(text));
// add your own code here
writeLn('The magic happens here.');
end;
end;
end.
Trickier…
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)
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^^
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.
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
writeLn(value^);
readLn;
namespace ConsoleApplication5;
type
TRec = record public X, Y, Z: Integer; end;
Program = class
public
class method Main(args: array of String): Int32;
begin
// 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"
writeLn(d);
end;
readLn;
end;
end;
end.
but i just noticed InstanceOffset is wrong for the 2nd and 3rd field, so I’ll be taking a look at that.