在Delphi记录中获取字段的偏移量运行时

7

给定一个记录类型:

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

如何获取一个字段的偏移量(以字符串形式给出字段名)?我需要在运行时进行此操作 - 要访问的字段名称在运行时决定。

示例:

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)^;
   ..

我正在使用Delphi 7。


@joe 是的,我知道这是什么味道... 我实际上并没有将 Textbox 绑定到这个问题上;那只是我能想到的最简单的例子来说明问题。它实际上是规则引擎的一部分,执行可以通过名称引用数据库字段的命令。问题在于,在执行此代码时,涉及的字段已经被加载到记录中了。 - Blorgbeard
最终我选择了手动维护的if-else语句,完全剔除了指针,并将“name:string”映射到“value:currency”。 - Blorgbeard
4个回答

8
您无法这样做。Delphi 7不会为记录类型生成RTTI信息。虽然有其他选项(如前面的答案所示),但这些选项需要手动映射“字段名称”->“偏移量”。

5

正如Alex所说,Delphi 7不会为记录类型生成RTTI信息,因此无法在运行时检索所需信息。但是,在之后的版本中(Delphi 2010+),它会生成,并且以下代码:

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 + '}';

将会产生'TItem {{name=UPC,offset=0}{name=Price,offset=24}{name=Cost,offset=32}}'

您还可以使用以下方式在特定实例中设置字段值(尽管您应该确保类型):

if fields[i].Name = 'Price' then
  fields[i].SetValue(@item, 10);

正确。换句话说,RTTI 为您自动和通用地处理所有类型,而您只能针对需要它的那些类型特定且最有效地处理。 - Deltics

4

以下内容适用于您的简化场景,但我怀疑可能无法为这种情况创建一个通用函数。

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;


1

这是你要找的吗?

 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;

不,我有一个字符串,比如'UPC'。我想使用反射或Delphi等效方法来获取该字符串命名的字段的值。 - Blorgbeard
该字符串可能是'UPC',也可能是'Price'或其他任何内容 - 直到运行时我才知道。 - Blorgbeard
我不能在运行时这样做。我的情况是用户将在文本框中输入“价格”或“UPC”或其他内容,然后我必须显示该字段的值(简化场景)。 - Blorgbeard
但是在这一行中 Inc(pc, GetItemFieldOffset('Cost'));你正在指定cost。你的意思是在运行时不知道是否要在这里指定cost还是price吗? - Bharat
是的,那样做可以 - 但出于各种原因,我不想使用 If-ElseIf 块。实际记录中有许多字段,如果更改了这些字段,我不想更新我的代码。 - Blorgbeard
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接