Co Delphi 7 RTTI wie i może być pobrana z TypeInfo(aRecordType)
jest:
- Nazwa typ rekordu;
- Rekordowy rozmiar globalny;
- Przesunięcie i typ każdej zmiennej zliczanej referencyjnie w rekordzie (łańcuch/wariant/najszerszy/tablica dynamiczna/inny rekord zagnieżdżony zawierający zmienne zliczane odnie sieniami).
Najnowsze informacje są niezbędne, aby zwolnić pamięć używaną przez każdą zmienną zliczaną przez odniesienie wewnątrz rekordu lub skopiować zawartość rekordu w czasie wykonywania. Inicjowanie rekordu jest również wykonywane w kodzie wygenerowanym przez kompilator (jeśli rekord jest tworzony na stosie), za pomocą metody _InitializeRecord()
, z wypełnieniem globalnym równym 0, gdy klasa lub tablica dynamiczna jest podana.
To samo dotyczy typów record
i object
we wszystkich wersjach Delphi.
Można zauważyć, że istnieje bug in modern version of Delphi (w tym przynajmniej Delphi 2009 i 2010), które czasem nie tworzą kodu do inicjowania obiektów na stosie. Będziesz musiał użyć rekordu, ale złamie kompatybilność z poprzednią wersją Delphi. :(
Oto struktura używana do przechowywania tych danych RTTI:
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;
Używając tych danych, tutaj jest na przykład to, co można zrobić:
Na przykład, oto jak dwa rekordy tego samego typu mogą być porównywane, za pomocą tego 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;
Więc dla celów, jakie Delphi 7 RTTI mógł poinformować, w czasie wykonywania, jest pozycja każdego ciągu w rekordzie. Stosując powyższy kod, można łatwo utworzyć funkcję za pomocą indeksu polu:
procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);
Ale po prostu nie mamy potrzebnych informacji do wykonania żądania:
- Nazwy pól nie są przechowywane w obrębie RTTI (tylko nazwa globalnego typu rekordu, a nawet nie zawsze AFAIK);
- Tylko pola z policzeniem referencyjnym mają przesunięcie, a nie inne pola typu prostego (jak liczba całkowita/podwójna ...).
Jeśli naprawdę potrzebujesz tej funkcji, jedynym rozwiązaniem w Delphi 7 jest używanie nie rekordów, ale klas.
W Delphi 7, jeśli utworzysz klasę z opublikowanymi polami, będziesz mieć wszystkie potrzebne informacje dla wszystkich opublikowanych pól. Następnie możesz zaktualizować tak opublikowaną treść pola. Oto, co robi środowisko wykonawcze VCL, gdy odserializujesz zawartość .dfm w instancjach klasy lub ORM approach.
+1 To bardzo dobre i jednoznacznie zadane pytanie. –
Ale jeśli istnieje potrzeba aktualizacji takich pól, to może dane powinny być przechowywane w sposób umożliwiający takie aktualizacje. Mam na myśli, że możesz pracować z polami zapisanymi jako łańcuchy w formacie podobnym do 'Name = Value', a do tego użyj TStringList lub TIniFile. czego mi brakuje? –
@Charles Czy przyjmiesz odpowiedź? Czy masz jeszcze więcej kłopotów? –