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.
精彩评论