开发者

Is there a way to update a field in a record knowing the field name and value

开发者 https://www.devze.com 2023-03-15 05:21 出处:网络
Given a Record: MyRecord = record Company: string; Address: string; NumberOfEmplyees: integer; can you write a function call like

Given a Record:

MyRecord = record
    Company: string;
    Address: string;
    NumberOfEmplyees: integer;

can you write a function call like

function UpdateField(var FieldName: string; FieldValue: variant): bool;

so that:

UpdateField('Company', 'ABC Co');

would update MyRecord.Company to 'ABC Co'?

I looked for an example but everything开发者_运维百科 I found is for a database. Any help pointing me in the right direction is appreciated.

Thanks, Charles


What Delphi 7 RTTI knows, and can be retrieved from TypeInfo(aRecordType), is:

  • The record type name;
  • The record global size;
  • The offset and type of each reference-counted variable within the record (string/variant/widestring/dynamic array/other nested record containing reference-counted variables).

The latest information is necessary to free the memory used by each reference-counted variables inside the record, or copy the record content, at run-time. The initialization of the record is also performed either in compiler-generated code (if the record is created on the stack), either via _InitializeRecord() method, either with a global fill to 0 when a class or a dynamic array is instanciated.

It's the same for both record and object types, in all version of Delphi.

You can note that there is a bug in modern version of Delphi (including Delphi 2009 and 2010 at least), which sometimes don't create the code for initializing objects on stack. You'll have to use record instead, but it will break compatibility with previous version of Delphi. :(

Here are the structure used for storing this RTTI data:

type
  TFieldInfo = packed record
    TypeInfo: ^PDynArrayTypeInfo; // information of the reference-counted type
    Offset: Cardinal; // offset of the reference-counted type in the record
  end;
  TFieldTable = packed record
    Kind: byte;
    Name: string[0]; // you should use Name[0] to retrieve offset of Size field
    Size: cardinal;  // global size of the record = sizeof(aRecord)
    Count: integer;  // number of reference-counted field info
    Fields: array[0..0] of TFieldInfo; // array of reference-counted field info
  end;
  PFieldTable = ^TFieldTable;

Using this data, here is for instance what you can do:

  • Some optimized low-level asm version of the Delphi 7 System.pas, corresponding to the records (including a faster CopyRecord function);
  • A way of accessing, saving and loading record content (also inside dynamic arrays, providing TList-like methods - and more - for a dynamic array of records).

For instance, here is how two records of the same type can be compared, using this RTTI:

/// check equality of two records by content
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties
// - will use binary-level comparison: it could fail to match two floating-point
// values because of rounding issues (Currency won't have this problem)
function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
var FieldTable: PFieldTable absolute TypeInfo;
    F: integer;
    Field: ^TFieldInfo;
    Diff: cardinal;
    A, B: PAnsiChar;
begin
  A := @RecA;
  B := @RecB;
  if A=B then begin // both nil or same pointer
    result := true;
    exit;
  end;
  result := false;
  if FieldTable^.Kind<>tkRecord then
    exit; // raise Exception.CreateFmt('%s is not a record',[Typ^.Name]);
  inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0]));
  Field := @FieldTable^.Fields[0];
  Diff := 0;
  for F := 1 to FieldTable^.Count do begin
    Diff := Field^.Offset-Diff;
    if Diff<>0 then begin
      if not CompareMem(A,B,Diff) then
        exit; // binary block not equal
      inc(A,Diff);
      inc(B,Diff);
    end;
    case Field^.TypeInfo^^.Kind of
      tkLString:
        if PAnsiString(A)^<>PAnsiString(B)^ then
          exit;
      tkWString:
        if PWideString(A)^<>PWideString(B)^ then
          exit;
      {$ifdef UNICODE}
      tkUString:
        if PUnicodeString(A)^<>PUnicodeString(B)^ then
          exit;
      {$endif}
      else exit; // kind of field not handled
    end;
    Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
    inc(A,Diff);
    inc(B,Diff);
    inc(Diff,Field^.Offset);
    inc(Field);
  end;
  if CompareMem(A,B,FieldTable.Size-Diff) then
    result := true;
end;

So for your purpose, what Delphi 7 RTTI could let you know at runtime, is the position of every string within a record. Using the code above, you could easily create a function using the field index:

     procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);

But you simply don't have the needed information to implement your request:

  • The field names are not stored within the RTTI (only the global record type name, and even not always AFAIK);
  • Only reference-counted fields have an offset, not other fields of simple type (like integer/double...).

If you really need this feature, the only solution under Delphi 7 is to use not records, but classes.

On Delphi 7, if you create a class with published fields, you'll have all needed information for all published fields. Then you can update such published field content. This is what the VCL runtime does when unserializing the .dfm content into class instances, or with an ORM approach.


You need modern versions of Delphi to do what you ask for without resorting to manually coding the lookups, e.g. via a table.

The updated RTTI introduced in Delphi 2010 can support what you are looking for, but there's nothing in Delphi 7 that will do this for records.

0

精彩评论

暂无评论...
验证码 换一张
取 消