目
录
===============================================================================
⊙DFM
文件与持续机制(persistent)
⊙ReadComponentResFile /
WriteComponentResFile
函数
⊙Delphi
持续机制框架简述
⊙一个
TForm
对象的创建过程
⊙TStream Class
和 TStream.ReadComponent
方法
⊙TReader Class
和 TReader.ReadRootComponent
方法
⊙TReader.ReadPrefix
方法
⊙TComponent.ReadState
虚方法
⊙TReader.ReadData
方法
⊙TReader.ReadDataInner
方法
⊙TReader.ReadProperty
方法
⊙TPersistent.DefineProperties
虚方法
⊙TReader.ReadComponent
方法
⊙TReader.ReadValue / TReader.NextValue
系列方法
⊙TReader.ReadStr
方法
⊙TReader.ReadInteger / ReadString /
ReadBoolean
系列方法
⊙TReader.Read
方法
⊙ObjectBinaryToText / ObjectTextToBinary
函数
===============================================================================
本文排版格式为:
(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)
正
文
===============================================================================
⊙DFM
文件与持续机制(persistent)
===============================================================================
我们在使用
Delphi
的 IDE
进行快速开发的时候,可以方便地从元件面板上拖放元件(component)至表单,完成表单的界面和事件设计。Delphi
将这些界面的设计期信息保存在表单相应的 DFM
文件中,方便程序员随时读取和修改。
DFM
文件根据元件在表单上的嵌套层次存放元件属性,以下是一个 DFM
文件的示例:
应用程序编译之后,DFM
文件的信息被二进制化了,这些二进制信息存储在应用程序的资源(resource)段中。每个表单(也就是
class)及表单上的元件在资源段中存储为与表单同名的资源,可以使用
FindResource API
获得。应用程序在运行期创建表单实例的时候,会从资源段中读取表单的属性,还原设计期的设置。这种将类型信息保存在文件中,并且可以在运行期恢复类型的操作,在本文中被称之为持续(persistent)机制。持续机制是
Delphi
成为 RAD
工具的原因之一。
持续机制和
RTTI
是紧密结合的,但本文不讨论 RTTI(关于
RTTI
可参考我前几天写的两篇笔记),只讨论实现持续机制的总体框架及相关类(class)。这些类包括
TStream、TFiler、TReader、TWriter、TParser、TPersisetent、TComponent、TCustomForm
等。
===============================================================================
⊙ReadComponentResFile /
WriteComponentResFile
函数
===============================================================================
让我们从一个比较直观的例子开始。
Classes.pas
中定义了两个函数 ReadComponentResFile
和 WriteComponentResFile,它们的功能是“把元件的属性信息保存到文件”和“从文件中恢复元件属性信息”。
先做个试验。新建一个项目,在
Form1
上放置两个 Button
和一个 Memo。Button
的 Click
事件代码如下。按 F9
运行该项目,先在 Memo1
中输入一些字符,然后按下 Button1,再按下
Button2,你会看一个新建的
Form。它的属性几乎和
Form1
一样,甚至连 Memo1
中的字符都保存下来了,唯一的不同只是它的 Name
属性变成了“Form1_1”。你可以查看
FORM1.RES
文件的内容看看 Delphi
是如何存储元件信息的。
WriteComponentResFile
函数的代码如下,它只是调用 Stream
对象的 WriteComponentRes
方法将对象属性保存到资源文件中的:
ReadComponentResFile
函数也是调用 Stream
的方法实现从文件中读取对属信息:
ReadComponentResFile
函数可以通过 Instance
参数传入对象句柄,也可以通过返回值获得对象句柄。Instance
参数只能是已实例化的对象或 nil。如果是
nil,那么
ReadComponentResFile
会自动根据文件信息创建对象实例,但必须使用RegisterClass
函数注册将要被载入的类,否则会触发异常。
有个类似的函数
ReadComponentRes,它从应用程序的资源段中恢复对象的属性信息。它的
ResName
参数就是表单类的名称:
===============================================================================
⊙Delphi
持续机制框架简述
===============================================================================
持续机制的实现必须由
IDE、编译器、表单类、元件类和辅助类合作完成。
这里的表单类不是指一般所指的
TForm class,在
Delphi
的帮助文件中,称之为“root class”。root class
是指能在设计期被 Form Designer
作为最上层编辑表单的类(如
TCustomForm、TFrame、TDataModule
等)。Delphi
在设计期将元件的published
属性的值保存在 .DFM
文件中,也只有 published
的属性才能被 Object Insepector
设置赋值。
Form Designer
设计的 root class
对象在编译时,Delphi
将对象的属性以及其所包含的元件的属性保存在应用程序的资源段(RT_RCDATA)中。
辅助类包括
TStream、TReader、TWriter、TParser
等。这些类起着中间层的作用,用于存储和读取对象属性的信息。虽然我称它们为辅助类,但是保存和恢复对象信息的实际操作是由它们完成的。
===============================================================================
⊙一个
TForm
对象的创建过程
===============================================================================
下面是一个典型的表单
Form1
的创建过程,缩进代表调用关系(Form1.ReadState
例外,防止缩进太多),带“?”的函数表示我尚未仔细考察的部分,带“*”表示元件编写者需要注意的函数。
Application.CreateForm(TForm1,
Form1);
过程简述:
TCustomForm.Create
函数中先调用 CreateNew
设置缺省的表单属性,然后调用Classes.InitInheritedComponent
函数。
InitInheritedComponent
用于初始化一个 root class
对象。该函数的功能就是从应用程序的资源中恢复设计期的表单信息。InitInheritedComponent
的声明如下:
InitInheritedComponent
传入两个参数:Instance
参数代表将要从资源段中恢复信息的对象,RootAncestor
表示该对象的祖先类。如果从资源中恢复信息成功,则返回 True,否则返回
False。InitInheritedComponent
通常只在 root class
的构造函数中调用。
InitInheritedComponent
调用自身内置的函数:InitComponent(Instance.ClassType)。InitComponent
先判断 Instance.ClassType
是否是 TComponent
或 RootAncestor,如果是则返回
False
并退出,否则调用 InternalReadComponentRes
* InitComponent
递归调用自己检查类信息。没看懂为什么要这样设计,如果有谁看懂了请告诉我。
InternalReadComponentRes
InternalReadComponentRes
ReadRootComponent
先调用 TReader.ReadSignature。ReadSignature
从 stream
中读取 4
字节的内容,如果读出来的内容不是 'TPF0',则触发异常(SInvalidImage),表示该
stream
的内容是错误的。然后 ReadRootComponent
调用 ReadPrefix
读取元件的状态信息。
如果
Root
参数是 nil,也就是说
Root
对象还没被创建,则直接从流中读取 Root
的类名,再使用 FindClass
函数找到该类在内存中的地址,并调用该类的构造函数创建
Root
的实例。
接下来
ReadRootComponent
调用 Root
的 ReadState
虚函数从流中读取 Root
对象的属性。TComponent.ReadState
只有一行代码:Reader.ReadData(Self);。
ReadData
调用 ReadDataInner
读取 root
元件及 root
的子元件的属性信息。
ReadDataInner
先循环调用 ReadProperty
从流中读取 root
元件的属性,直到遇到 EndOfList
标志(vaNull)。ReadProperty
使用 RTTI
函数,将从流中读出的数据设置为对象的属性。ReadProperty
中还调用了 Instance.DefineProperties,用于实现自定义的属性存储。ReadDataInner
然后循环调用 ReadComponent(nil)
读取子元件的信息。
ReadComponent
的执行过程与 ReadRootComponent
的过程很相似,它根据流中的信息使用FindComponentClass
找到元件类在内存中的地址,然后调用该元件类的构造函数创建对象,接下来调用新建对象的 ReadState -> TReader.ReadData ->
ReadDataInner -> TReader.ReadProperty,重复
ReadRootComponent
的过程。
TReader.ReadComponent
和 TComponent.ReadState
形成递归调用过程,把表单上嵌套的元件创建出来。
最后
InitInheritedComponent
函数返回,一个root class
对象从资源中实例化的过程完成。
====================================
⊙TStream Class
和 TStream.ReadComponent
方法
===========================================