Delphi's component reading and writing mechanism

Source: Internet
Author: User

Delphi's component reading and writing mechanism (i.)
I. Introduction to streaming objects (stream) and read-write objects (Filer)
Object-based data management plays an important role in object-oriented programming. In Delphi, the way to support the object-type data management is one of its main features.
Delphi is an integrated development environment that combines object-oriented visual design with object-oriented language. The core of Delphi is the component. A component is one of the objects. Delphi applications are constructed entirely of components, so developing high-performance Delphi applications will inevitably involve object-type data management techniques.
Object-type data management consists of two things:
Using objects to manage data
Management of various types of data objects, including objects and components
Delphi boils down to object-type data management classes as stream objects (stream) and Filer objects (Filer) and applies them to all aspects of the visual Component Class Library (VCL). They provide rich functionality for managing objects in memory, external memory, and Windows resources, stream objects, also known as streaming objects, Tstream, Thandlestream, TFileStream, Tmemorystream, Collectively known as Tresourcestream and Tblobstream. They represent the ability to store data on various mediums, which abstract the management operations of various data types (including objects and components) in memory, external memory, and database fields into object methods, and take advantage of object-oriented technology to make it fairly easy for applications to copy data across various stream objects.
Read-Write objects (Filer) include Tfiler objects, Treader objects, and Twriter objects. The Tfiler object is the base object for file reads and writes, and is used primarily by Treader and Twriter in the application. Both Treader and Twriter objects inherit directly from the Tfiler object. The Tfiler object defines the basic properties and methods of the Filer object.
The Filer object mainly accomplishes two major functions:
Accessing components in form files and form files
Provides data buffering for faster data read and write operations
For streaming objects and read-write objects have a perceptual understanding, first look at an example.
A) write a file
Procedure Tfomr1.writedata (Sender:tobject);
Var
Filestream:tfilestream;
Mywriter:twriter;
I:integer
Begin
Filestream:=tfilestream.create (' C:\Test.txt ', fmopenwrite);//Create a file stream object
Mywriter:=twriter.create (filestream,1024); To connect Mywriter and FileStream.
Mywriter.  Writelistbegin; Write list start flag
For i:=0 to Memo1.lines.count-1 do
Mywriter.writestring (Memo1.lines[i]); Save text information in memo components to a file
Mywriter.writelistend; Write list End flag
Filestream.seek (0,sofrombeginning); File stream object pointer moves to stream start position
Mywriter.free; Releasing the Mywriter object
Filestream.free; Releasing the FileStream object
End;
b) Read the file
Procedure Tform1.readdata (Sender:tobject);
Var
Filestream:tfilestream;
Myreader:treader;
Begin
Filestream:=tfilestream.create (' C:\Test.txt ', fmopenread);
Myreader:=trreader.create (filestream,1024); To connect myreader and FileStream.
Myreader.readlistbegin; Write the list start sign read out
Memo1.lines.clear; Clears the text content of the Memo1 component
While a myreader.endoflist do//Note a method of Treader: Endoflist
Begin
Memo1.lines.add (myreader.readstring); Add the read-out string to the Memo1 component
End;
Myreader.readlistend; Write the end of the list to read the sign
Myreader.free; Releasing the Myreader object
Filestream.free; Releasing the FileStream object
End;
The above two processes, one for the writing process, the other for the reading process. The writing process, by Twriter, uses TFileStream to save the contents of a memo (text message) as a binary file stored on disk. The reading process, in contrast to the writing process, uses TFileStream to convert the contents of the binary file into textual information and display it in the memo by Treader. The running program can see that the read process faithfully restores the information saved by the writing process.
It is important to note that read-write objects such as Tfiler objects, Treader objects, and Twriter objects are seldom called directly by the application writer, and are often used to read and write the state of a component, which plays a critical role in the mechanism of read-write components.
For streaming object stream, a lot of references are described in detail, and Tfiler objects, Treader objects and Twriter objects, especially the component reading and writing mechanism of reference is very rare, this article will be through the VCL source code tracking to the component read-write mechanism analysis.
Second, read-write Object (Filer) and component reading and writing mechanism
The Filer object is primarily used to access the Delphi form files and the components in the form file, so it is important to understand the structure of the Delphi form file (DFM file) clearly for the Filer object.
The DFM file is used for the Delphi storage form. The form is the core of Delphi visual programming. The form corresponds to the window in the Delphi application, the visual components in the form correspond to the interface elements in the window, non-visual components such as TTimer and Topendialog, corresponding to a function of the Delphi application. The design of the Delphi application is actually centered on the design of the form. Therefore, the DFM file also occupies an important position in the Delphi application design. All elements in a form, including the properties of the form itself, are included in the DFM file.
In the Delphi application window, the interface elements are interconnected according to their own relationships, so the tree structure is the most natural form of expression, and accordingly, the components in the form are organized in a tree-like structure, and in the DFM file, the relationship is expressed. The DFM file is physically stored in text (previously stored as a binary file in the Delphi2.0 version), logically in the form of a tree structure that arranges the relationships of the components. From this text, you can see the form's tree structure. Here is the content of the DFM file:
Object Form1:tform1
left = 197
Top = 124
......
PixelsPerInch = 96
TextHeight = 13
Object Button1:tbutton
left = 272
......
Caption = ' Button1 '
TabOrder = 0
End
Object Panel1:tpanel
left = 120
......
Caption = ' Panel1 '
TabOrder = 1
Object Checkbox1:tcheckbox
Left = 104
......
Caption = ' CheckBox1 '
TabOrder = 0
End
End
End
This DFM file is twriter by streaming object stream to generate, of course, there is a binary file to the text information file conversion process, this conversion process is not the object to be studied in this article, so ignore such a process.
When the program begins to run, Treader reads the form and the component through the stream object stream, because when Delphi compiles the program, it compiles the DFM file information into the executable using the compile instruction {$R *.DFM}. So what Treader reads is actually information about forms and components that are compiled into an executable file.
Treader and Twriter not only read and write most of the standard data types in Object Pascal, but also read and write to advanced types such as List, Variant, and even read and write Perperties and component. However, TReader, Twriter itself actually provides limited functionality, and most of the actual work is done by Tstream, a very powerful class. That is to say Treader, Twriter is actually just a tool, it is only responsible for how to read and write components, as for the specific read and write operation is done by Tstream.

Since Tfiler is the common ancestor class for Treader and Twriter, the first is to understand Treader and Twriter, starting with Tfiler.
Tfiler
First look at the definition of the Tfiler class:
Tfiler = Class (TObject)
Fstream:tstream;
Fbuffer:pointer;
Fbufsize:integer;
Fbufpos:integer;
Fbufend:integer;
Froot:tcomponent;
Flookuproot:tcomponent;
Fancestor:tpersistent;
Fignorechildren:boolean;
Protected
Procedure Setroot (value:tcomponent); Virtual
Public
Constructor Create (Stream:tstream; Bufsize:integer);
destructor Destroy; Override
Procedure DefineProperty (const name:string; Readdata:treaderproc; Writedata:twriterproc; Hasdata:boolean); Virtual Abstract
Procedure Definebinaryproperty (const name:string; ReadData, Writedata:tstreamproc; Hasdata:boolean); Virtual Abstract
Procedure Flushbuffer; Virtual Abstract
Property Root:tcomponent read Froot write setroot;
Property Lookuproot:tcomponent read Flookuproot;
Property Ancestor:tpersistent read Fancestor write fancestor;
Property Ignorechildren:boolean read Fignorechildren write Fignorechildren;
End
The Tfiler object is an abstract class of Treader and Twriter that defines the basic properties and methods for component storage. It defines the root property, and root specifies a component that is read or written, and its create method takes the stream object as an incoming parameter to establish a connection to the stream object, and the specific read and write operations of the Filer object are done by the stream object. Therefore, the Filer object can access the component as long as it is accessible by the stream object.
The Tfiler object also provides two public methods for defining properties: DefineProperty and Definebinaryproperty, which enable an object to read and write to properties that are not defined in the component published section. Here are some highlights of these two methods.
The DefineProperty () method is used to persist standard data types such as strings, integers, booleans, characters, floats, and enumerations.
In the DefineProperty method. The name parameter specifies the names of the properties that should be written to the DFM file, which is not defined in the published section of the class.
The ReadData and WriteData parameters specify methods for reading and writing the required data when accessing an object. The types of readdata and WriteData parameters are Treaderproc and Twriterproc, respectively. These two types are declared as follows:
Treaderproc = procedure (Reader:treader) of object;
Twriterproc = procedure (writer:twriter) of object;
The HasData parameter at run time determines whether the property has data to be stored.
The Definebinaryproperty method and DefineProperty have many similarities, which are used to store binary data, such as sounds and images.
Here's how these two methods are used.
We put a non-visual component on the form such as TTimer, when we reopen the form we find that TTimer is still in the original place, but TTimer does not have left and top properties Ah, then its location information is saved where?
Open the DFM file for the form, and you can see a few lines of content similar to the following:
Object Timer1:ttimer
left = 184
Top = 149
End
Delphi's streaming system can only save published data, but TTimer does not have published left and top properties, so how is this data saved?
TTimer is a derived class of tcomponent, and in the Tcomponent class we find a function like this:
Procedure Tcomponent.defineproperties (Filer:tfiler);
Var
Ancestor:tcomponent;
Info:longint;
Begin
Info: = 0;
Ancestor: = Tcomponent (Filer.ancestor);
If Ancestor <> nil then Info: = Ancestor.fdesigninfo;
Filer.defineproperty (' left ', Readleft, Writeleft,
Longrec (Fdesigninfo). Lo <> Longrec (Info). Lo);
Filer.defineproperty (' Top ', Readtop, Writetop,
Longrec (Fdesigninfo). Hi <> Longrec (Info). Hi);
End
Tcomponent's Defineproperties is a method that overrides its ancestor class Tpersistent, which is an empty virtual method in the Tpersistent class.
In the Defineproperties method, we can see that there is a filer object as its argument, when the property is defined, it references the Ancestor property, and if the property is not empty, the object should read and write only the values of the different properties inherited from ancestor. It calls Tfiler's DefineProperty method and defines the Readleft,writeleft,readtop,writetop method to read and write the left and top properties.
Therefore, any component derived from tcomponent, even if it does not have a left and top property, will have two properties in the streaming to the DFM file.
In the process of finding data, it is found that there is very little material involved in the component reading and writing mechanism. Because the component's write process was done in the design phase by the Delphi IDE, it was not possible to track its running process. So the author is through the process of tracking the VCL source code to understand the components of the reading mechanism, but also through the reading mechanism and Twriter to analyze the component writing mechanism. So the following will follow this thinking process to describe the component reading and writing mechanism, first speaking Treader, then Twriter.
TReader
First look at the Delphi project file, you will find a few lines of code like this:
Begin
Application.initialize;
Application.createform (TForm1, Form1);
Application.Run;
End.
This is the entrance to the Delphi program. Simply say the meaning of these lines of code: Application.initialize The necessary initialization work for the application that started running, Application.createform (TForm1, Form1) to create the necessary forms, The Application.Run program starts running and enters the message loop.
Now we are most concerned about the creation of the form of this sentence. How is the form and the components on the form created? As mentioned earlier: all components in a form include the properties of the form itself in the DFM file, and when Delphi compiles the program, the DFM file information is compiled into the executable using the compile instruction {$R *.DFM}. Therefore, it can be concluded that the creation of the form will need to read the DFM information, what to read it, of course, Treader!
By following the steps of the program, you can see that the program called the Treader Readrootcomponent method during the creation of the form. The function of this method is to read out the root component and all the components it owns. Consider the implementation of this method:
function Treader.readrootcomponent (root:tcomponent): tcomponent;
......
Begin
Readsignature;
Result: = nil;
Globalnamespace.beginwrite; Loading from Stream adds to name space
Try
Try
Readprefix (Flags, I);
If Root = Nil Then
Begin
Result: = Tcomponentclass (Findclass (READSTR)). Create (nil);
Result.name: = Readstr;
End Else
Begin
Result: = Root;
READSTR; {Ignore class name}
If csdesigning in Result.componentstate then
Readstr Else
Begin
Include (Result.fcomponentstate, csloading);
Include (Result.fcomponentstate, csreading);
Result.name: = Finduniquename (READSTR);
End
End
Froot: = Result;
Ffinder: = Tclassfinder.create (Tpersistentclass (Result.classtype), True);
Try
Flookuproot: = Result;
G: = globalloaded;
If G <> Nil Then
floaded: = G Else
floaded: = tlist.create;
Try
If Floaded.indexof (Froot) < 0 Then
Floaded.add (Froot);
Fowner: = Froot;
Include (Froot.fcomponentstate, csloading);
Include (Froot.fcomponentstate, csreading);
Froot.readstate (self);
Exclude (Froot.fcomponentstate, csreading);
If G = Nil Then
For I: = 0 to Floaded.count-1 do tcomponent (Floaded[i]). Loaded;
Finally
If G = nil then floaded.free;
floaded: = nil;
End
Finally
Ffinder.free;
End
......
Finally
Globalnamespace.endwrite;
End
End
Readrootcomponent first calls Readsignature to read the Filer object label (' TPF0 '). Detecting tags before loading objects can prevent inadvertent reading of invalid or outdated data.
Look again at Readprefix (flags, I), the function of the Readprefix method is very similar to the readsignature, except that it is the flag (PreFix) that precedes the component in the read stream. When a write object writes a component to a stream, it pre-writes two values before the component, and the first value is a flag that indicates whether the component is an inherited form from an ancestor form and whether its position in the form is important or not, and the second value indicates the order in which it was created in the ancestor form.
Then, if the root parameter is nil, a new component is created with the class name read out by Readstr, and the Name property of the component is read out of the stream; otherwise, the class name is ignored and the name attribute is determined to be unique.
Froot.readstate (self);
This is a critical sentence, the ReadState method reads the properties of the root component and the components it owns. Although this readstate method is a Tcomponent method, further tracking can be found that it actually ultimately locates the Readdatainner method of Treader, the implementation of the method is as follows:
Procedure Treader.readdatainner (instance:tcomponent);
Var
Oldparent, Oldowner:tcomponent;
Begin
While not endoflist do ReadProperty (Instance);
Readlistend;
Oldparent: = Parent;
Oldowner: = Owner;
Parent: = instance.getchildparent;
Try
Owner: = Instance.getchildowner;
If not Assigned (owner) then owner: = Root;
While not endoflist do readcomponent (nil);
Readlistend;
Finally
Parent: = oldparent;
Owner: = Oldowner;
End
End
There is this line of code:
While not endoflist do ReadProperty (Instance);
This is used to read the properties of the root component, for properties, as mentioned earlier, both the published property of the component itself, and the non-published property, such as left and top of TTimer. For these two different properties, there should be two different reading methods, in order to verify the idea, we look at the implementation of the ReadProperty method.
Procedure Treader.readproperty (ainstance:tpersistent);
......
Begin
......
Propinfo: = Getpropinfo (Instance.classinfo, fpropname);
If Propinfo <> nil then Readpropvalue (Instance, Propinfo) Else
Begin
{Cannot reliably recover from an error in a defined property}
Fcanhandleexcepts: = False;
Instance.defineproperties (self);
Fcanhandleexcepts: = True;
If Fpropname <> ' then
Propertyerror (Fpropname);
End
......
End
To save space, omit some code, here's a description: Fpropname is the name of the property read from the file.
Propinfo: = Getpropinfo (Instance.classinfo, fpropname);
This code is the information that obtains the published property Fpropname. As you can see from the next code, if the property information is not empty, the property value is read by the Readpropvalue method, and the Readpropvalue method reads the property value through the Rtti function, which is no longer described in detail. If the property information is empty, the Description property Fpropname is non-published, it must be read by another mechanism. This is the Defineproperties method mentioned earlier, as follows:
Instance.defineproperties (self);
The method actually calls the DefineProperty method of the Treader:
Procedure Treader.defineproperty (const name:string;
Readdata:treaderproc; Writedata:twriterproc; Hasdata:boolean);
Begin
If Sametext (Name, Fpropname) and Assigned (ReadData) Then
Begin
ReadData (self);
Fpropname: = ';
End
End
It first compares whether the Read property name is the same as the preset property name, and calls the ReadData method to read the property value if the same and the Read method ReadData is not empty.
Well, the root component has already been read, and it's time to read the components owned by the root component. Then look at the method:
Procedure Treader.readdatainner (instance:tcomponent);
The method is followed by a code like this:
While not endoflist do readcomponent (nil);
This is exactly what is used to read the sub-components. The read mechanism of the subcomponent is the same as the read of the root component described above, which is a deep traversal of a tree.
So far, the reading mechanism of the components has been introduced.
Then look at the write mechanism of the component. When we add a component to a form, its associated properties are stored in the DFM file, which is done by Twriter.

Twriter
A Twriter object is an filer object that can be instantiated to write data to a stream. The Twriter object inherits directly from the Tfiler, and in addition to overwriting methods inherited from Tfiler, it adds a lot of methods for writing various data types, such as Integer, String, and component.
The Twriter object provides a number of ways to write various types of data into the stream, and the Twrite object writes data to the stream in a different format depending on the data. Therefore, to master the implementation and application of Twriter object, it is necessary to understand the format of the writer object storing data.
The first thing to note is that each filer object's stream contains a Filer object label. The label accounts for four bytes with a value of "TPF0". The Filer object accesses the label for the Writesignature and Readsignature methods. This tag is used primarily for reading data (components, etc.) of reader objects and directs the read operation.
Second, the writer object keeps a byte flag before storing the data to indicate what type of data is stored behind it. The byte is a value of type Tvaluetype. Tvaluetype is an enumeration type that occupies a byte space and is defined as follows: Software Development Network
Tvaluetype = (Vanull, valist, VaInt8, VaInt16, VaInt32, vaentended, vastring, Vaident,
Vafalse, Vatrue, Vabinary, Vaset, valstring, Vanil, vacollection);
Therefore, the writer object of each write data method, in the implementation, must first write the flag bit and then write the corresponding data, and the reader object of each read data method to read the flag bit to judge, if the reading data, otherwise, produce a read data invalid exception event. The VALIST flag has a special purpose, and it is used to identify items that will be followed by a series of identical types, while the flag that identifies the end of a continuous project is vanull. Therefore, when the writer object writes successively several identical items, writes the VALIST flag with the Writelistbegin first, writes the data item, then writes out the Vanull sign, but reads this data, starts with the Readlistbegin, Readlistend ends, The middle uses the Ndoflist function to determine whether there is a vanull flag.
Take a look at a very important method of Twriter WriteData:
Procedure Twriter.writedata (instance:tcomponent);
......
Begin
......
Writeprefix (Flags, Fchildpos);
If Usequalifiednames Then
Writestr (Gettypedata (Ptypeinfo (Instance.ClassType.ClassInfo)). Unitname '. ' INSTANCE.CLASSNAME) Software Development Network
Else
Writestr (Instance.classname);
Writestr (Instance.name);
Propertiesposition: = Position;
if (fancestorlist <> nil) and (Fancestorpos < Fancestorlist.count) Then
Begin
If Ancestor <> nil then INC (Fancestorpos);
INC (Fchildpos);
End
WriteProperties (Instance);
Writelistend;
......
End
From the WriteData method we can see the overview of generating DFM file information. Write the flag (PREFIX) before the component, and then write the class name, instance name. Then there is a statement like this:
WriteProperties (Instance);
This is used to write the properties of the component. As mentioned earlier, in the DFM file, both the published property and the non-published property, the two properties should be written differently. To see the implementation of WriteProperties:
Procedure Twriter.writeproperties (instance:tpersistent);
......
Begin
Count: = Gettypedata (instance.classinfo) ^. Propcount;
If Count > 0 Then
Begin
Getmem (proplist, Count * SIZEOF (Pointer));
Try
Getpropinfos (Instance.classinfo, proplist);
For I: = 0 to Count-1 do
Begin
Propinfo: = Proplist^[i];
If Propinfo = Nil Then
break;
If Isstoredprop (Instance, Propinfo) Then
WriteProperty (Instance, propinfo);
End
Finally
Freemem (proplist, Count * SIZEOF (Pointer));
End
End
Instance.defineproperties (self);
End
Take a look at the following code:
If Isstoredprop (Instance, Propinfo) Then
WriteProperty (Instance, propinfo);
The function Isstoredprop determines whether the property needs to be saved by storing the qualifier, and if it needs to be saved, it calls WriteProperty to save the property, and WriteProperty is implemented through a series of rtti functions.
The published property is saved after saving the non-published attribute, which is done by this code:
Instance.defineproperties (self);
The implementation of Defineproperties has already been mentioned, and the left and top properties of TTimer are saved through it.
Well, so far there is a question: how do the subcomponents of the root component are saved? Then look at the WriteData method (which is mentioned earlier):
Procedure Twriter.writedata (instance:tcomponent);
......
Begin
......
If not Ignorechildren then
Try
if (Fancestor <> nil) and (Fancestor is tcomponent) then
Begin
if (Fancestor is tcomponent) and (Csinline in Tcomponent (Fancestor). Componentstate) Then
Frootancestor: = Tcomponent (Fancestor);
Fancestorlist: = tlist.create;
Tcomponent (Fancestor). GetChildren (Addancestor, frootancestor);
End
If csinline in Instance.componentstate then
Froot: = Instance;
Instance.getchildren (Writecomponent, Froot);
Finally
Fancestorlist.free;
End
End
The Ignorechildren property enables a writer object to store components without storing subcomponents owned by that component. If the Ignorechildren property is true, the writer object stores the component without the child components it owns. Otherwise, the child components will be stored.
Instance.getchildren (Writecomponent, Froot);
This is the most critical sentence to write the sub-component, it takes the Writecomponent method as a callback function, according to the principle of depth-first traversal tree, if the root component Froot the existence of subcomponents, then use Writecomponent to save its subcomponents. So what we see in the DFM file is a tree-like component structure.

http://blog.csdn.net/henreash/article/details/7274717

http://blog.csdn.net/henreash/article/details/7280322

Delphi's component reading and writing mechanism

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.