Reputation: 2757
I'm wondering how I can perform serialization of a generic TObjectList<T>
container. Basically, I want to store different objects in that list, but all objects will descend from TSerializable
, which is defined as follows:
TSerializable = class abstract(TObject)
public
{ Public declarations }
procedure LoadFromStream(const S: TStream); virtual; abstract;
procedure SaveToStream(const S: TStream); virtual; abstract;
end;
Now, let's say I have these classes defined somewhere in my app:
type
TExampleClass = class(TSerializable)
private
{ Private declarations }
FIntProp: Integer;
public
{ Public declarations }
constructor Create();
procedure LoadFromStream(const S: TStream); override;
procedure SaveToStream(const S: TStream); override;
property IntProp: Integer read FIntProp write FIntProp;
end;
TAnotherExample = class(TSerializable)
private
{ Private declarations }
FStringProp: String;
public
{ Public declarations }
constructor Create();
procedure LoadFromStream(const S: TStream); override;
procedure SaveToStream(const S: TStream); override;
procedure ReverseStringProp();
property StringProp: String read FStringProp write FStringProp;
end;
I'm planning to store such objects in a list:
var
MS: TMemoryStream;
SomeList: TObjectList<TSerializable>;
begin
MS := TMemoryStream.Create();
SomeList := TObjectList<TSerializable>.Create(True);
try
SomeList.Add(TExampleClass.Create());
SomeList.Add(TAnotherClass.Create());
TExampleClass(SomeList[0]).IntProp := 1992;
TAnotherClass(SomeList[1]).StringProp := 'Some value';
// Here, a method to serialize the list...
SerializeList(SomeList, MS);
// Clear the list and reset position in the stream.
SomeList.Clear();
MS.Seek(0, soFromBeginning);
// Unserialize the list.
UnserializeList(SomeList, MS);
// Should display "Some value".
Writeln(TAnotherClass(SomeList[1]).StringProp);
finally
SomeList.Free();
MS.Free();
end;
end;
Now, how could I possibly serialize the whole list to stream and then re-create the list from that stream?
What I was thinking about was:
SaveToStream()
on that object.But for that approach to work, I would need to create some kind of a class register, which would be some kind of a dictionary to store known classes. It sounds like a good idea, but then I would need to call some RegisterClass()
method to add every new class to the dictionary, and I don't like that way too much.
Is there any other way, or should I just do it the way I proposed?
Thanks a bunch.
Upvotes: 0
Views: 3568
Reputation: 2757
Thank you guys for tips. I have decided to use my own approach, which is probably not the best one, but suits the needs of my small project.
I thought that someone might be interested in such approach, so I posted it here.
Basically, what I decided on is to have a base class TSerializable
:
type
TSerializable = class abstract(TObject)
public
{ Public declarations }
procedure LoadFromStream(const S: TStream); virtual; abstract;
procedure SaveToStream(const S: TStream); virtual; abstract;
end;
Every descendant class needs to implement LoadFromStream()
and SaveToStream()
and handle saving to stream separately. It would be probably good to write some generic methods, which would load/save all class properties automatically.
Then, I have this small class:
type
TSerializableList = class(TObjectList<TSerializable>)
public
procedure Serialize(const S: TStream);
procedure UnSerialize(const S: TStream);
end;
The code is:
{ TSerializableList }
procedure TSerializableList.Serialize(const S: TStream);
var
CurrentObj: TSerializable;
StrLen, StrSize: Integer;
ClsName: String;
begin
S.Write(Self.Count, SizeOf(Integer));
for CurrentObj in Self do
begin
ClsName := CurrentObj.QualifiedClassName();
StrLen := Length(ClsName);
StrSize := SizeOf(Char) * StrLen;
S.Write(StrLen, SizeOf(Integer));
S.Write(StrSize, SizeOf(Integer));
S.Write(ClsName[1], StrSize);
CurrentObj.SaveToStream(S);
end;
end;
procedure TSerializableList.UnSerialize(const S: TStream);
var
I, NewIdx, TotalCount, Tmp, Tmp2: Integer;
ClsName: String;
Context: TRttiContext;
RttiType: TRttiInstanceType;
begin
Context := TRttiContext.Create();
try
S.Read(TotalCount, SizeOf(Integer));
for I := 0 to TotalCount -1 do
begin
S.Read(Tmp, SizeOf(Integer));
S.Read(Tmp2, SizeOf(Integer));
SetLength(ClsName, Tmp);
S.Read(ClsName[1], Tmp2);
RttiType := (Context.FindType(ClsName) as TRttiInstanceType);
if (RttiType <> nil) then
begin
NewIdx := Self.Add(TSerializable(RttiType.MetaclassType.Create()));
Self[NewIdx].LoadFromStream(S);
end;
end;
finally
Context.Free();
end;
end;
Quick and dirty, but works for what I need.
NOTE
Since the code uses extended RTTI, it won't compile in older Delphi versions. Also, you might need to add {$STRONGLINKTYPES ON}
in your DPR file or invent some other mechanism, so that linker doesn't skip your classes (David Heffernan suggest one way here)
Upvotes: 1