Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How to use Component Serialization Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
16-Jul-03
Category
VCL-General
Language
Delphi 5.x
Views
200
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			 Author: Yoav Abrahami

Serialization is the process of saving a component state (To a file of stream). 
Delphi provides a nice infrastructure for serialization of components (The DFM 
Way). But how do we utilize this infrastructure to the fullest? What are the 
limitations?

Answer:

Introduction 

In order to understand serialization, we need to define what serialization is, what 
we want to save (the component state) and how do we use the mechanism if it exists. 
Only after understanding those concepts, we can continue to learn how to write 
components to use this infrastructure. 

Serialization: I define serialization components as the process of taking a 
component, saving the component state, so we can reconstruct another component 
later that is identical to the original component. I do not know if there is a 
formal definition to serialization, and my definition my not be the best, but for 
this article, it is enough. An object that can be serialized is sometimes called 
persistent object. In Delphi, all components are by default persistent (with some 
limitations I’ll talk about later in this article). 

Component State: A Component state is what distinguishes a component from another 
component of the same type. If two components have the same state, we can replace 
one with the other without any change in the application. One can say that the 
state of a component is the algebraic sum of it’s properties. 

Serializing a component in Delphi is a simple process, using the stream classes. To 
save a component to some media, all we need to do is create the appropriate stream, 
and save the component to the stream. In order to load the component, we need only 
to create the stream object, and then read the component. 

Example of saving a component to file: 

1   procedure TForm1.SaveComponent;
2   var
3     Stream: TFileStream;
4   begin
5     Stream := TFileStream.Create('c:\temp\mycomponent.dat', fmCreate);
6     try
7       Stream.WriteComponent(MyComponent);
8     finally
9       Stream.Free;
10    end;
11  end;
12   
13  //Example of loading a component from the file: 
14  
15  procedure TForm1.F;
16  var
17    Stream: TFileStream;
18    MyComponent: TComponent;
19  begin
20    Stream := TFileStream.Create('c:\temp\mycomponent.dat', fmOpenRead);
21    try
22      Stream.ReadComponent(MyComponent);
23    finally
24      Stream.Free;
25    end;
26  end;

 
Special conversion functions

Two special functions must be mentioned. ObjectBinaryToText and ObjectTextToBinary. 
Those two functions manipulate streams; can convert the stream content between the 
binary representation and a text (DFM like) representation. Those functions are 
very useful to debug streaming of object, and to provide readable streams. 

Example of saving a component to a text file: 

27  procedure TForm1.SaveComponent;
28  var
29    Stream2: TFileStream;
30    Stream1: TMemoryStream;
31  begin
32    Stream1 := TMemoryStream.Create;
33    Stream2 := TFileStream.Create('c:\temp\mycomponent.dat', fmCreate);
34    try
35      Stream1.WriteComponent(MyComponent);
36      Stream1.position := 0;
37      ObjectBinaryToText(Stream1, Stream2);
38    finally
39      Stream1.Free;
40      Stream2.Free;
41    end;
42  end;

 
Component Support for serialization

We tend to think that components are serialization ready. In general, that is true. 
A Component will know how to serialize all of it’s published properties (unless 
they are of type TComponent, I’ll explain later why). Moreover, 3rd-party 
components we use normally are serialization ready, hiding the messy stuff. 
However, if you are a component writer, and you need to create a serialization 
ready component, you need to go into a partially documented area. In the rest of 
this article, this is what I will discuss. 

Components know how to serialize all published properties that are of atom types 
(string, char, integer and the such), TPersistent descendent objects (but not 
components). TComponent also defines a vast infrastructure to serialize more types 
of data. I know of 5 methods, each with its uses, advantages and disadvantages 
(There may be more methods in the VCL that I have overlooked). 

Extending components using TPersistent. 
Extending components using TCollection. 
Extending components using DefineProperties Override. 
Extending components using Child Components (Component Composition). 
Extending components using Component Aggregation. 

Note: The names I gave to those methods are not taken from Borland Documentation or 
any other source. Those names are the names I use to identify the various 
serialization methods, and you are welcome to disagree with the names    

1. Extending components using TPersistent. 

This method of is useful for composition relation between a TComponent object and 
one TPersistent object. This method is available in both Delphi 5 and 6. 
A Component will stream by default any property of type TPersistent that is not a 
TComponent. Our TPersistent property is streamed just like a component, and it may 
have other TPersistent properties that will get streamed. 

The VCL makes the assumption that the property always has an object created. If we 
do not initialize the TPersistent object before we try to read the parent 
component, we will get an error. 

Advantage: 

The simplest method to support compositions. 

Disadvantages: 

Cannot stream TComponent derived properties. 
Cannot be used in a polymorph property (a property that the object is points to may 
be of different classes in different situations). 
The TPersistent object must be created in the constructor of the parent TComponent. 

Example: 

See TpersistentExampleXX Unit in the example code. 

43  type
44    TPersistentExampleRoot = class(TComponent)
45    private
46      FBranch: TPersistentExampleChild;
47      FC: string;
48      procedure SeTPersistentExampleChild(const Value: TPersistentExampleChild);
49      procedure SetC(const Value: string);
50    public
51      constructor Create(AOwner: TComponent); override;
52      destructor Destroy; override;
53    published
54      property Branch: TPersistentExampleChild read FBranch write
55        SeTPersistentExampleChild;
56      property C: string read FC write SetC;
57    end;
58  
59    TPersistentExampleChild = class(TPersistent)
60    private
61      FB: string;
62      procedure SetB(const Value: string);
63    public
64    published
65      property B: string read FB write SetB;
66    end;

 
I define two classes, one a TComponent, another as a TPersistent. I Set the 
TComponent to reference the TPersistent. That’s all. 

2. Extending components using TCollection. 

This method is useful for composition relation between a TComponent and one or more 
TPersistent objects. This method is available in both Delphi 5 and 6. 
A TComponent will stream any published property that is a TCollection. The great 
thing, is that with almost no work you can serialize a list of objects. 

I am not going to provide a full explanation of this method, as it is documented 
well in the Delphi help files. 

Advantages: 

Provides a simple method to stream a list of TPersistent objects. 

Disadvantages: 

All the objects must be of a single class, derived from TCollectionItem. 
Cannot stream TComponent derived objects. 

Example: 

See CollectionExampleXX Unit in the example code. 

3. Extending components using DefineProperties Override. 

This method allows the definition of semi-properties. Semi-properties are not real 
properties, but are treated as properties by the Delphi streaming system. This 
method applies to both Delphi 5 and 6. 

DefineProperties has two major uses – when you need to stream properties that are 
not normally supported by Delphi (like array properties), or when you need to 
customize the method a property is streamed. 

How does it work: 

You must override the DefineProperties method (defined in the TPersistent class), 
and in the derived function you need to call the DefineProperty or 
DefineBinaryProperty of the Filer parameter. 
You need to pass two methods to the DefineXXX functions, one for reading the 
property value, the other to write the value. 
In those two functions, you get a TReader and TWriter objects as parameters, and 
you are free to read and write whatever you want. The only limitation is that the 
reader and the writer will traverse the same number of bytes. 

Advantage:
 
Allows more control over streaming properties. 
Allows streaming of any type of data. 

Disadvantages: 

Requires more work – for each sub-property we must write two methods. 
When saving some types of data (like TComponents), ObjectBinaryToText fails. 
When saving TComponents, references from the saved TComponent to other objects may 
not be restored (referenced from within the saved component properties tree to 
objects outside it will not be restored). 

Example: 

See the DefinePropertiesExampleXX Unit in the example code. 
In this example, I define an object who streams two outrival properties – An array 
and a TComponent. 
The Class declaration is: 

67  type
68    TDefinePropertiesExample = class(TComponent)
69    private
70      FIntegers: array of Integer;
71      FChild: TComponent;
72      procedure ReadIntegers(Reader: TReader);
73      procedure ReadChild(Reader: TReader);
74      procedure WriteIntegers(Writer: TWriter);
75      procedure WriteChild(Writer: TWriter);
76      function GetIntegers(Index: Integer): Integer;
77      procedure SetIntegers(Index: Integer; const Value: Integer);
78    protected
79      procedure DefineProperties(Filer: TFiler); override;
80    public
81      constructor Create(AOwner: TComponent); override;
82      destructor Destroy; override;
83      property Integers[Index: Integer]: Integer read GetIntegers write SetIntegers;
84      property Child: TComponent read FChild write FChild;
85    end;
86   
87  {Take special notice to the DefineProperties Function override, and to the Read… 
88  and Write… Functions. 
89  The DefineProperties function: }
90  
91  procedure TDefinePropertiesExample.DefineProperties(Filer: TFiler);
92  begin
93    inherited;
94    Filer.DefineProperty('Integers', ReadIntegers, WriteIntegers, True);
95    Filer.DefineProperty('IntegersCount', ReadIntegerCount, WriteIntegerCount, True);
96    // If we do not reference a child component, do not save any. (If we
97    // try, we will get an error).
98    Filer.DefineProperty('Child', ReadChild, WriteChild, FChild <> nil);
99  end;
100  
101 //And the write / read functions: 
102 
103 procedure TDefinePropertiesExample.ReadChild(Reader: TReader);
104 begin
105   Reader.ReadComponent(FChild);
106 end;
107 
108 procedure TDefinePropertiesExample.ReadIntegerCount(Reader: TReader);
109 begin
110   // read the length of the array.
111   SetLength(FIntegers, Reader.ReadInteger);
112 end;
113 
114 procedure TDefinePropertiesExample.ReadIntegers(Reader: TReader);
115 var
116   I: Integer;
117 begin
118   // write the integers in the array.
119   Reader.ReadListBegin;
120   I := Low(FIntegers);
121   while not Reader.EndOfList do
122   begin
123     FIntegers[i] := Reader.ReadInteger;
124     Inc(I);
125   end;
126   Reader.ReadListEnd;
127 end;
128 
129 procedure TDefinePropertiesExample.WriteChild(Writer: TWriter);
130 begin
131   Writer.WriteComponent(FChild);
132 end;
133 
134 procedure TDefinePropertiesExample.WriteIntegerCount(Writer: TWriter);
135 begin
136   // write the length of the array.
137   Writer.WriteInteger(Length(FIntegers));
138 end;
139 
140 procedure TDefinePropertiesExample.WriteIntegers(Writer: TWriter);
141 var
142   I: Integer;
143 begin
144   // write the integers in the array.
145   Writer.WriteListBegin;
146   for I := Low(FIntegers) to High(FIntegers) do
147     Writer.WriteInteger(FIntegers[i]);
148   Writer.WriteListEnd;
149 end;

 
4. Extending components using Child Components (Component Composition). 

This method is available only in Delphi 6. 
The method allows to stream child components that have a composition relation with 
the parent component. The method is very similar to method 1 (TPersisent), and is 
in fact an extension of that method. 
Don’t get confused – In Delphi 5 you cannot serialize a child TComponent easily.  
You will have to use DefineProperties (method 3), or by Component Aggregation 
(method 5). 

How does it work: 

Each TComponent has a property ComponentStyle of type TComponentStyle. This type is 
a set of some flags. One of those flags is csSubComponent. A Component who has this 
flag set will be serialized by this method. 

The method has the same advantages and disadvantages as the TPersistent method (1). 

Example: 

See the SubComponentExampleXX Unit in the example code. 
First, we must create the SubComponent in the constructor of the parent component. 
150 
151 constructor TSubComponentExRoot.Create(AOwner: TComponent);
152 begin
153   inherited;
154   FSomeString := 'This is the root component';
155   FChild := TSubComponentExChild.Create(Self);
156 end;
157  
158 then, we need to tell the SubComponent that it is a SubComponent (when we want it 
159 serialized). 
160 
161 procedure TSubComponentExRoot.SetChildComponentFlag(Value: Boolean);
162 begin
163   FChild.SetSubComponent(Value);
164 end;

 

5. Extending components using Component Aggregation. 

This method is available both in Delphi 5 and 6. 
The method allows streaming any number of child components, without the limitation 
that we need to know the number in advance or the limitation that we need to create 
the child components in the constructor of the root component. This is what makes 
this method different then the others – it serializes child components and not 
sub-components. Delphi Forms, DataModules and Frames are using this method to save 
their state to the DFM files. 
This method has some variations between Delphi 5 and 6 (primarily in the fixup 
stage. 

How does it work? 

Saving the child components: 

In the TComponent class we have the GetChildren function. A component we wishes to 
serialize it’s child components needs to override this function, and call the proc 
parameter function for each child component. 

Reading the child components: 

When reading the root components, all of the child components will be read, and 
added to it’s components array. The root component will be the owner of all the 
components read, regardless of who where their owner before we wrote them. You are 
assures that the components will be read completely with all the data you wish, BUT 
there is a tricky part . 

References between the components read and from the components read to other 
components are another matter. In Delphi documentation and sources this is called 
the fixup stage – fixing the references between the read components. There are two 
types of fixups – local and global. 

Local fixup is restoring references between components read at the same time (two 
components on the same form, for example). The trick here is that both components 
have to be owned by the root component before we saved them. Take a good look at 
the example application and play with the owners of the child components, to see 
when those references are restored and when they are not. 

Global fixups is the process of restoring references between the read components 
and some other components already existing. Delphi has a method to locate those 
other existing component in the classes unit that changed between Delphi 5 and 6. 

In Delphi 5, the global fixup process uses a function pointer called 
FindGlobalComponent. In the forms unit, This pointer is set to point to a function 
called FindGlobalComponent. This function uses a global list of all forms and 
datamodules to find those components. In order to extend the global fixup to 
support our objects, we need to replace this function and restore it, and it is a 
messy code. 

In Delphi 6, Borland fixed this spaghetti, by replacing the FindGlobalComponent 
function pointer with a function, that it using a list of Find Component function. 
We can now register out own find component function to co-exist with the Delphi 6 
‘forms unit’ function. The register functions are RegisterFindGlobalComponentProc 
and UnRegisterFindGlobalComponentProc. 
There is a lot more to say on the fixup subject, and I hope someone will take the 
time to explain it better. 

Advantage: 

Allows streaming of full dynamic component trees. 
Allows restoring complicated referenced between saved components and to other 
components in the application. 

Disadvantages: 

Complicated and easily broken (normally we do not mind who the owner of a component 
is, but here is has a strong affect). 
The fixup process is verry complicated and I find it hard to use. 

Example Code: 

See the ComponentAggregationExampleXX Unit in the example code. 

The GetChildren Function: 
165 
166 procedure TComponentFirstChild.GetChildren(Proc: TGetChildProc;
167   Root: TComponent);
168 begin
169   inherited;
170   if (FSecondChild <> nil) and SaveChild then
171     Proc(FSecondChild);
172 end;

 




			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC