7

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.

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
  • @joe yeah, I know how it smells.. I'm not actually tying a Textbox to this; that was just the simplest example I could think of to illustrate the problem. It's actually part of a sort of rules engine, which executes commands which can refer to database fields by name. The problem is that the fields in question have already been loaded into a record when this code executes. – Blorgbeard Jul 05 '10 at 20:08
  • I went with a manually maintained if-else in the end, cutting out the pointer thing entirely and just mapping `name : string` to `value : currency`. – Blorgbeard Jul 05 '10 at 20:14

4 Answers4

8

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
  • 1,136
  • 6
  • 5
5

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
  • 826
  • 7
  • 8
  • 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 Jul 03 '10 at 23:28
4

Following would work for your simplified scenario but I doubt it will be possible to make a generic function for this kind of thing.

The best I can think if is to add some kind of registration object but it still would require you to register all the records you need an offset from.

function GetItemFieldOffset(const Value: string): Integer;
var
  item: TItem;
begin
  if Value = 'UPC' then Result := 0
  else if Value = 'Price' then Result := Integer(@item.Price) - Integer(@item)
  else if Value = 'Cost' then Result :=  Integer(@item.Cost) - Integer(@item)
  else raise Exception.CreateFmt('Unhandled condition (%0:s)', [Value]);
end;
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
1

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
  • 6,828
  • 5
  • 35
  • 56
  • 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 Jul 02 '10 at 08:54
  • The string could be 'UPC', or it could be 'Price' or anything else - I won't know until runtime. – Blorgbeard Jul 02 '10 at 08:54
  • I hope that i understood wrong. But ShowMessage(myRecordptr.Price) will display 100. So what you need is just replace UPC with price. – Bharat Jul 02 '10 at 09:03
  • 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 Jul 02 '10 at 09:06
  • 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 Jul 02 '10 at 09:07
  • will the if condition solve your problem. Check the edited post – Bharat Jul 02 '10 at 09:11
  • 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 Jul 02 '10 at 09:13
  • Re: not updating - I'm afraid you can't avoid that in D7. – Uli Gerhardt Jul 02 '10 at 11:02
  • 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). – Uli Gerhardt Jul 02 '10 at 11:05