"downstream" entries in delphi?

I know that you cannot write anything from the record at all, but I'm not sure how to summarize my problem in one sentence. Change the title if you do.

What I want to do here is to create an array of some general type, which can be one of the X types, the array will be filled with these custom types (they have different fields and that is important). The easy way is to create only an array of record variants, each variant has its own type, but obviously cannot update identifiers as follows:

GenericRec = Record case SubTypeName: TSubTypeName of type1name: (SubRec: Type1); type2name: (SubRec: Type2); ... typeNname: (SubRec: TypeN); end; 

Changing SubRec to SubRec1, SubRec2... SubRecN makes the link painful, but not impossible. And since I started looking for alternative solutions to the above problem, classes came in handy.

An obvious example of what I'm trying to achieve is TObject , an array of them can be assigned to many different things. This is what I want, but with the records (and this is impossible to do), because I want to be able to save the records to a file and also read them back (also because this is what I already know). Creating my own simple class is not a problem, which is why the descendant class will be my subtype - I can do it. But what about writing this file and reading it? It comes down to serialization, which I have no idea how to do this. From what I am compiling, it is not so simple, and the class should come from TComponent .

 TMyClass = Class 

Does it really matter if I make a class as above? This is nothing unusual and has no more than 10 fields, including several custom types.

Setting serialization aside (just because I have a lot of reading to do on this topic), using classes here is also out of the question.

At this point, what are my options? Should I discard entries and try this with classes? Or would it be much less difficult to just stick to the notes and deal with the “restriction” option? I’m all about studying, and if the explosion of the class approach can make me smarter, I will do it. I also just looked at TList too (never used it), but it seems that it doesn't mix well with records, maybe it can be done, but it could be from my league at the moment. I am open to any suggestions. What am I doing?

+8
delphi records
source share
3 answers

A “natural” way of processing such data is to use class rather than record . It will be much easier to work with it, both during the definition and during implementation: in particular, the virtual methods are very effective for setting up a process for a particular class. Then use TList/TObjectList TCollection or TCollection , or the common array in newer versions of Delphi to store the list.

On serialization, there are several ways to do this. See Delphi: storing data in some structure structure

In your particular case, complexity arises from the “variant” type of record you are using. IMHO the main drawback is that the compiler will refuse to set any variable of the reference type (for example, a string ) in the "variant" part. Thus, you can only write “simple” variables (for example, integer ) in this “variant” part. A big IMHO limitation that reduces interest in this solution.

Another possibility might be to save the record type at the beginning of its definition, for example. with RecType: integer or even better with RecType: TEnumerationType , which will be more explicit than a number. But you will have to write a lot of code manually and work with pointers that are slightly error prone if you are not very fluent in pointer coding.

That way, you can also save the record type information available through TypeInfo(aRecordVariable) . You can then use FillChar to initialize the contents of the record to zero immediately after allocation, and then use the following function to complete the recording of contents immediately after disallocation (this is what Dispose() does internally and you will call it, otherwise you will leak memory):

 procedure RecordClear(var Dest; TypeInfo: pointer); asm jmp System.@FinalizeRecord end; 

But such an example of implementation just invents a wheel! The class is actually implemented: the first element of any TObject instance is a pointer to its ClassType :

 function TObject.ClassType: TClass; begin Pointer(Result) := PPointer(Self)^; end; 

In Delphi, there is another structure called object . This is a kind of record , but it supports inheritance - see this article . This is the old style of OOP programming in Turbo Pascal 5.5 days, but is deprecated but accessible. Note that I found a strange compilation error in newer versions of Delphi : sometimes the object allocated on the stack is not always initialized.

Take a look at our TDynArray packaging and its related features, which can serialize any record content into binary or JSON. See the Delphi (win32) serialization library . It will work with variant records, even if they include string in their non-invariant part, while a simple "Write / BlockWrite" will not work with counted fields.

+5
source share

You combine serialization with "writing everything to disk with a single BlockWrite call BlockWrite " You can serialize whatever you want, regardless of whether it comes from TComponent or TPersistent .

Despite the fact that recording everything with just one call to BlockWrite looks convenient at first, you will quickly find it not what you want if your desired types of records will store something especially interesting (for example, strings, dynamic arrays, interfaces, objects or other types based on pointers or pointers).

You will probably also find the recording options unsatisfactory as you will encode the lowest common denominator. You won’t be able to access anything in the record without checking the actual type of content, and even the smallest amount of data will occupy the same amount of space as the largest data type.

The question seems to describe polymorphism, so you can also accept what the language already provides for this. Use an array (or list or any other container) of objects. Then you can use virtual methods to treat them evenly. You can implement dynamic sending for records if you want (for example, give each record a pointer to a function that references a function that knows how to deal with this record containing a data type), but in the end, you probably just find yourself rethinking classes.

+6
source share

To do this with records, you will create different types of records that have a common field in front, and then put the same fields in the shared record. Then you can simply type a pointer to a shared record to a pointer to a specific record, when necessary. For example:

 type PGenericRec = ^GenericRec; GenericRec = Record RecType: Integer; end; PType1Rec = ^Type1Rec; Type1Rec = Record RecType: Integer; // Type1Rec specific fields... end; PType2Rec = ^Type2Rec; Type2Rec = Record RecType: Integer; // Type2Rec specific fields... end; PTypeNRec = ^TypeNRec; TypeNRec = Record RecType: Integer; // TypeNRec specific fields... end; var Recs: array of PGenericRec; Rec1: PType1Rec; Rec2: PType2Rec; RecN: PTypeNRec; I: Integer; begin SetLength(Recs, 3); New(Rec1); Rec1^.RecType := RecTypeForType1Rec; // fill Rec1 fields ... Recs[0] := PGenericRec(Rec1); New(Rec2); Rec2^.RecType := RecTypeForType2Rec; // fill Rec2 fields ... Recs[1] := PGenericRec(Rec2); New(RecN); Rec3^.RecType := RecTypeForTypeNRec; // fill RecN fields ... Recs[2] := PGenericRec(RecN); for I := 0 to 2 do begin case Recs[I]^.RecType of RecTypeForType1Rec: begin Rec1 := PType1Rec(Recs[I]); // use Rec1 as needed... end; RecTypeForType1Re2: begin Rec2 := PType2Rec(Recs[I]); // use Rec2 as needed... end; RecTypeForTypeNRec: begin RecN := PTypeNRec(Recs[I]); // use RecN as needed... end; end; end; for I := 0 to 2 do begin case Recs[I]^.RecType of RecTypeForType1Rec: Dispose(PType1Rec(Recs[I])); RecTypeForType2Rec: Dispose(PType2Rec(Recs[I])); RecTypeForTypeNRec: Dispose(PTypeNRec(Recs[I])); end; end; end; 

As for serialization, you do not need TComponent for this. You can serialize records, you just need to do it manually. To record, first write the RecType value, and then write the following values ​​for recording. To read, first read the RecType value, then create the appropriate record type for that value, then read the values ​​related to the record in it:

 interface type PGenericRec = ^GenericRec; GenericRec = Record RecType: Integer; end; NewRecProc = procedure(var Rec: PGenericRec); DisposeRecProc = procedure(Rec: PGenericRec); ReadRecProc = procedure(Rec: PGenericRec); WriteRecProc = procedure(const Rec: PGenericRec); function NewRec(ARecType: Integer): PGenericRec; procedure DisposeRec(var Rec: PGenericRec); procedure ReadRec(Rec: PGenericRec); procedure WriteRec(const Rec: PGenericRec); procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc); implementation type TRecTypeReg = record RecType: Integer; NewProc: NewRecProc; DisposeProc: DisposeRecProc; ReadProc: ReadRecProc; WriteProc: WriteRecProc; end; var RecTypes: array of TRecTypeReg; function NewRec(ARecType: Integer): PGenericRec; var I: Integer; begin Result := nil; for I = Low(RecTypes) to High(RecTypes) do begin with RecTypes[I] do begin if RecType = ARecType then begin NewProc(Result); Exit; end; end; end; raise Exception.Create('RecType not registered'); end; procedure DisposeRec(var Rec: PGenericRec); var I: Integer; begin for I = Low(RecTypes) to High(RecTypes) do begin with RecTypes[I] do begin if RecType = Rec^.RecType then begin DisposeProc(Rec); Rec := nil; Exit; end; end; end; raise Exception.Create('RecType not registered'); end; procedure ReadRec(var Rec: PGenericRec); var LRecType: Integer; I: Integer; begin Rec := nil; LRecType := ReadInteger; for I = Low(RecTypes) to High(RecTypes) do begin with RecTypes[I] do begin if RecType = LRecType then begin NewProc(Rec); try ReadProc(Rec); except DisposeProc(Rec); raise; end; Exit; end; end; end; raise Exception.Create('RecType not registered'); end; procedure WriteRec(const Rec: PGenericRec); var I: Integer; begin for I = Low(RecTypes) to High(RecTypes) do begin with RecTypes[I] do begin if RecType = Rec^.RecType then begin WriteInteger(Rec^.RecType); WriteProc(Rec); Exit; end; end; end; raise Exception.Create('RecType not registered'); end; procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc); begin SetLength(RecTypes, Length(RecTypes)+1); with RecTypes[High(RecTypes)] do begin RecType := ARecType; NewProc := ANewProc; DisposeProc := ADisposeProc; ReadProc := AReadProc; WriteProc := AWriteProc; end; end; end. 

.

 type PType1Rec = ^Type1Rec; Type1Rec = Record RecType: Integer; Value: Integer; end; procedure NewRec1(var Rec: PGenericRec); var Rec1: PType1Rec; begin New(Rec1); Rec1^.RecType := RecTypeForType1Rec; Rec := PGenericRec(Rec1); end; procedure DisposeRec1(Rec: PGenericRec); begin Dispose(PType1Rec(Rec)); end; procedure ReadRec1(Rec: PGenericRec); begin PType1Rec(Rec)^.Value := ReadInteger; end; procedure WriteRec1(const Rec: PGenericRec); begin WriteInteger(PType1Rec(Rec)^.Value); end; initialization RegisterRecType(RecTypeForType1Rec, @NewRec1, @DisposeRec1, @ReadRec1, @WriteRec1); 

.

 type PType2Rec = ^Type2Rec; Type2Rec = Record RecType: Integer; Value: Boolean; end; procedure NewRec2(var Rec: PGenericRec); var Rec2: PType2Rec; begin New(Rec2); Rec2^.RecType := RecTypeForType2Rec; Rec := PGenericRec(Rec2); end; procedure DisposeRec2(Rec: PGenericRec); begin Dispose(PType2Rec(Rec)); end; procedure ReadRec2(Rec: PGenericRec); begin PType2Rec(Rec)^.Value := ReadBoolean; end; procedure WriteRec2(const Rec: PGenericRec); begin WriteBoolean(PType2Rec(Rec)^.Value); end; initialization RegisterRecType(RecTypeForType2Rec, @NewRec2, @DisposeRec2, @ReadRec2, @WriteRec2); 

.

 type PTypeNRec = ^Type2Rec; TypeNRec = Record RecType: Integer; Value: String; end; procedure NewRecN(var Rec: PGenericRec); var RecN: PTypeNRec; begin New(RecN); RecN^.RecType := RecTypeForTypeNRec; Rec := PGenericRec(RecN); end; procedure DisposeRecN(Rec: PGenericRec); begin Dispose(PTypeNRec(Rec)); end; procedure ReadRecN(Rec: PGenericRec); begin PTypeNRec(Rec)^.Value := ReadString; end; procedure WriteRecN(const Rec: PGenericRec); begin WriteString(PTypeNRec(Rec)^.Value); end; initialization RegisterRecType(RecTypeForTypeNRec, @NewRecN, @DisposeRecN, @ReadRecN, @WriteRecN); 

.

 var Recs: array of PGenericRec; procedure CreateRecs; begin SetLength(Recs, 3); NewRec1(Recs[0]); PRecType1(Recs[0])^.Value : ...; NewRec2(Recs[1]); PRecType2(Recs[1])^.Value : ...; NewRecN(Recs[2]); PRecTypeN(Recs[2])^.Value : ...; end; procedure DisposeRecs; begin for I := 0 to High(Recs) do DisposeRec(Recs[I]); SetLength(Recs, 0); end; procedure SaveRecs; var I: Integer; begin WriteInteger(Length(Recs)); for I := 0 to High(Recs) do WriteRec(Recs[I]); end; procedure LoadRecs; var I: Integer; begin DisposeRecs; SetLength(Recs, ReadInteger); for I := 0 to High(Recs) do ReadRec(Recs[I]); end; 
+2
source share

All Articles