views:

115

answers:

4

Given a record type:

TItem = record
   UPC : string[20];
   Price : Currency;
   Cost : Currency;
   ...
end; 

And the name of a field as a string, how can I get the offset of that field within the record? I need to do this at runtime - the name of the field to access is decided at runtime.

Example:

var
   pc : Integer;
   fieldName : string;
   value : Currency;
begin
   pc := Integer(@item);                    // item is defined and filled elsewhere
   fieldName := Text1.Text;                 // user might type 'Cost' or 'Price' etc
   Inc(pc, GetItemFieldOffset(fieldName));  // how do I implement GetItemFieldOffset?
   value := PCurrency(pc)^;
   ..

I'm using Delphi 7.

+1  A: 

Is this what you are looking for

 type
   TItem = record
     UPC : string[20];
     Price : Currency;
     Cost : Currency;
     ...
   end; 

 var
   myRecord    : TItem ;
   myRecordPtr : ^TItem ;

 begin
   myRecord.price:= 100;
   myRecord.UPC := '111';
   myRecordPtr := @myRecord;
   if edit1.text = 'UPC' then   
     ShowMessage(myRecordptr.UPC);  // Displays '111'
   else if edit1.text = 'price' then   
     ShowMessage(myRecordptr.Price);  // Displays '100'
 end;
Bharat
Nope, I have a string like 'UPC'. I want to use reflection or the delphi equivalent to get the value of the field that the string names.
Blorgbeard
The string could be 'UPC', or it could be 'Price' or anything else - I won't know until runtime.
Blorgbeard
I hope that i understood wrong. But ShowMessage(myRecordptr.Price) will display 100. So what you need is just replace UPC with price.
Bharat
I can't do that at *runtime* though. I have a situation where the user will type in a textbox "Price" or "UPC" or whatever, and then I must display the value of that field (simplified scenario).
Blorgbeard
But in this line Inc(pc, GetItemFieldOffset('Cost')); you are specifying cost.Did you mean that you dont know whether to specify cost or price here until run time
Bharat
Correct - I'll update my question to make that more obvious.
Blorgbeard
will the if condition solve your problem. Check the edited post
Bharat
Yes that will work - but for various reasons I don't want to use an If-ElseIf block. There are many fields in the real record, and I don't want to have to update my code if it changes.
Blorgbeard
Re: not updating - I'm afraid you can't avoid that in D7.
Ulrich Gerhardt
You can avoid the if-else cascade if you use some kind of registry (e.g. `procedure AddField(const AName string; AOffset: Integer);` which could write to a stringlist).
Ulrich Gerhardt
+3  A: 
Lieven
+7  A: 

You can't. Delphi 7 does not emit RTTI for records. There are other options (as seen the previous answers) but those require manual mapping of "Field Name" -> "Offset".

alex
+2  A: 

As alex said, Delphi 7 doesn't emit RTTI for records, so you can't retrieve the required info at runtime. However, in later versions (Delphi 2010+) it does, and the following code:

TItem = record
   UPC : string[20];
   Price : Currency;
   Cost : Currency;
//...
end;
    var
       rttiContext: TRttiContext;
       rttiType: TRttiType;
       fields: TArray<TRttiField>;
       item: TItem;
    begin
        rttiType := rttiContext.GetType(TypeInfo(TItem));
        caption := rttiType.Name + ' {';
        fields := rttiType.GetFields;
        for i := low(fields) to high(fields) do
        begin
          caption := caption +'{name='+fields[i].Name+',';
          caption := caption +'offset='+IntToStr(fields[i].Offset)+'}';
        end;
        caption := caption + '}';

will produce 'TItem {{name=UPC,offset=0}{name=Price,offset=24}{name=Cost,offset=32}}'

You can also set the field value in a particular instance (although you should really also verify the type) using:

if fields[i].Name = 'Price' then
  fields[i].SetValue(@item, 10);
Dan Bartlett
Right. Put another way, RTTI does for you automatically and generically for all types what you can do for yourself specifically and most efficiently for only those types that need it.
Deltics