时间: 2021-07-31 作者:daque
一、流式东西(stream)和读写东西(filer)的引见 在面向东西步调安排中,东西式数据处置占领很要害的位置。在delphi中,对东西式数据处置的扶助办法是其第一次全国代表大会特性。 delphi是一个面向东西的可视化安排与面向东西的谈话相贯串的集成开拓情况。delphi的中心是组件。组件是东西的一种。delphi运用步调实足是由组件来结构的,所以开拓高本能的delphi运用步调必定会波及东西式数据处置本领。
东西式数据处置囊括两上面的实质:● 用东西来处置数据● 对各类数据东西(囊括东西和组件)的处置
delphi将东西式数据处置类归纳为stream东西(stream)和filer东西(filer),并将它们运用于可视组件类库(vcl)的方上面面。它们供给了充分的在外存、外部存储器和windows资源中处置东西的功效, stream东西,又称流式东西,是tstream、thandlestream、tfilestream、tmemorystream、tresourcestream和tblobstream等的统称。它们辨别代办了在百般媒体上保存数据的本领,它们将百般数据典型(囊括东西和组件) 在外存、外部存储器和数据库字段中的处置操纵笼统为东西本领,而且充溢运用了面向东西本领的便宜,运用步调不妨十分简单地在百般stream东西中正片数据。 读写东西(filer)囊括tfiler东西、treader东西和twriter东西。tfiler东西是文献读写的普通东西,在运用步调中运用的主假如treader和twriter。treader和twriter东西都径直从tfiler东西接受。tfiler东西设置了filer东西的基础属性和本领。 filer东西重要实行两大功效:● 存取窗体文献和窗体文献中的组件● 供给数据缓冲,加速数据读写操纵
为了对流式东西和读写东西有一个感性的看法,先来看一个例子。a)写文献procedure tfomr1.writedata (sender: tobject); r;var filestream:tfilestream; mywriter:twriter; i: integerbegin filestream:=tfilestream.create(‘c:\test.txt’,fmopenwrite);//创造文献流东西 mywriter:=twriter.create(filestream,1024); //把mywriter和filestream接洽起来 mywriter. writelistbegin; //写出列表发端标记 for i:=0 to memo1.lines.count-1 do mywriter.writestring(memo1.lines[i]); //生存memo组件华文本消息到文献中 mywriter.writelistend; //写出列表中断标记 filestream.seek(0,sofrombeginning); //文献流东西南针移到流开始场所 mywriter.free; //开释mywriter东西 filestream.free; //开释filestream东西end; b)读文献procedure tform1.readdata(sender: tobject);var filestream:tfilestream; myreader:treader;begin filestream:=tfilestream.create(‘c:\test.txt’,fmopenread); myreader:=trreader.create(filestream,1024); //把myreader和filestream接洽起来 myreader.readlistbegin; //把写入的列表发端标记读出来 memo1.lines.clear; //废除memo1组件的文本实质 while not myreader.endoflist do //提防treader的一个本领:endoflist begin memo1.lines.add(myreader.readstring); //把读出的字符串加到memo1组件中 end; myreader.readlistend; //把写入的列表中断标记读出来 myreader.free; //开释myreader东西 filestream.free; //开释filestream东西end; 上头两个进程,一个为写进程,另一个为读进程。写进程经过twriter,运用tfilestream把一个memo中的实质(文本消息)存为一个生存在磁盘上的二进制文献。读进程恰巧和写进程差异,经过treader,运用tfilestream把二进制文献中的实质变换为文本消息并表露在memo中。运路途序不妨看到,读进程淳厚的把写进程所生存的消息举行了恢复。 下图刻画了数据东西(囊括东西和组件)、流式东西和读写东西之间的联系。 图(一)
犯得着提防的是,读写东西如tfiler东西、treader东西和twriter东西等很少由运用步调编写者举行径直的挪用,它常常用来读写组件的状况,它在读写组件体制中表演着特殊要害的脚色。对于流式东西stream,很多参考材料上都有很精细的引见,而tfiler东西、treader东西和twriter东西更加是组件读写体制的参考材料则很罕见,正文将经过对vcl原代码的盯梢而对组件读写体制举行领会。
二、读写东西(filer)与组件读写体制 filer东西重要用来存取delphi的窗体文献和窗体文献中的组件,以是要领会地领会filer东西就要领会delphi 窗体文献(dfm文献)的构造。 dfm文献是用来delphi保存窗体的。窗体是delphi可视化步调安排的中心。窗体对应delphi运用步调中的窗口,窗体中的可视组件对应窗口中的界面元素,非可视组件如ttimer和topendialog,对应delphi运用步调的某项功效。delphi运用步调的安排本质上是以窗体的安排为重心。所以,dfm文献在delphi运用安排中也占很要害的场所。窗体中的一切元素囊括窗体自己的属性都包括在dfm文献中。 在delphi运用步调窗口中,界面元素是按具有联系彼此接洽的,所以树状构造是最天然的表白情势;相映地,窗体中的组件也是按树状构造构造;对应在dfm文献中,也要表白这种联系。dfm文献在物理上,是以文本办法保存的(在delphi2.0本子往日是保存为二进制文献的),在论理上则是以树状构造安置各组件的联系。从该文本中不妨看清窗体的树状构造。底下是dfm文献的实质: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 endend 这个dfm文献即是twriter经过流式东西stream来天生的,固然这边再有一个二进制文献到文本消息文献的变换进程,这个变换进程不是正文要接洽的东西,以是忽视如许的一个进程。 在步调发端运转的功夫,treader经过流式东西stream来读取窗体及组件,由于delphi在编写翻译步调的功夫,运用编写翻译训令{$r *.dfm}仍旧把dfm文献消息编写翻译到可实行文献中,所以treader读取的实质本质上是被编写翻译到可实行文献中的相关窗体和组件的消息。 treader和twriter不只不妨读取和写入object pascal中绝大局部规范数据典型,并且不妨读写list、variant等高档典型,以至不妨读写perperties和component。然而,treader、twriter自己本质上供给的功效很有限,大局部本质的处事是由tstream这个特殊宏大的类来实行的。也即是说treader、twriter本质上不过一个东西,它不过控制如何去读写组件,至于简直的读写操纵是由tstream来实行的。 因为tfiler是treader和twriter的大众前辈类,由于要领会treader和twriter,仍旧先从tfiler发端。tfiler
先来看一下tfiler类的设置:
tfiler = class(tobject)
private
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;
tfiler东西是treader和twriter的笼统类,设置了用来组件保存的基础属性和本领。它设置了root属性,root指领会所读或写的组件的根东西,它的create本领将stream东西动作传入参数以创造与stream东西的接洽, filer东西的简直读写操纵都是由stream东西实行。所以,只假如stream东西所能考察的媒体都能由filer东西存取组件。
tfiler 东西还供给了两个设置属性的public本领:defineproperty和definebinaryproperty,这两个本领使东西能读写不在组件published局部设置的属性。底下中心引见一下这两个本领。
defineproperty ( )本领用来使规范数据典型长久化,诸如字符串、平头、布尔、字符、浮点和列举。
在defineproperty本领中。name参数用来指定应写入dfm文献的属性的称呼,该属性不在类的published局部设置。
readdata和writedata参数指定在存取东西时读和写所需数据的本领。readdata参数和writedata参数的典型辨别是treaderproc和twriterproc。这两个典型是如许证明的:
treaderproc = procedure(reader: treader) of object;
twriterproc = procedure(writer: twriter) of object;
hasdata参数在运转时确定了属性能否罕见据要保存。
definebinaryproperty本领和defineproperty有很多的沟通之处,它用来保存二进制数据,如声响和图象等。
底下来证明一下这两个本领的用处。
咱们在窗体上放一个非可视化组件如ttimer,从新翻开窗体时咱们创造ttimer仍旧在从来的场合,但ttimer没有left和top属性啊,那么它的场所消息生存在何处呢?
翻开该窗体的dfm文献,不妨看到有一致如次的几行实质:
object timer1: ttimer
left = 184
top = 149
end
delphi的流体例只能生存published数据,但ttimer并没有published的left和top属性,那么那些数据是如何被生存下来的呢?
ttimer是tcomponent的派生类,在tcomponent类中咱们创造有如许的一个因变量:
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的defineproperties是掩盖了它的前辈类tpersistent的本领,在tpersistent类中该本领为空的虚本领。
在defineproperties本领中,咱们不妨看出,有一个filer东西动作它的参数,当设置属性时,它援用了ancestor属性,即使该属性非空,东西该当只读写与从ancestor接受的各别的属性的值。它挪用tfiler的defineproperty本领,并设置了readleft,writeleft,readtop,writetop本领来读写left和top属性。
所以,但凡从tcomponent派生的组件,纵然它没有left和top属性,在流化到dfm文献中,城市生存如许的两个属性。
在搜索材料的进程中,创造很罕见材料波及到组件读写体制的。因为组件的写进程是在安排阶段由delphi的ide来实行的,所以没辙盯梢它的运转进程。以是笔者是经过在步调运转进程中盯梢vcl原代码来领会组件的读体制的,又经过读体制和twriter来领会组件的写体制。以是下文将依照这一思想进程来报告组件读写体制,先讲treader,尔后是twriter。treader
先来看delphi的工程文献,会创造一致如许的几行代码:
begin
application.initialize;
application.createform(tform1, form1);
application.run;
end.
这是delphi步调的进口。大略的说一下这几行代码的意旨:application.initialize对发端运转的运用步调举行少许需要的初始化处事,application.createform(tform1, form1)创造需要的窗体,application.run步调发端运转,加入动静轮回。
此刻咱们最关怀的是创造窗体这一句。窗体以及窗体上的组件是如何创造出来的呢?在前方仍旧提到过:窗体中的一切组件囊括窗体自己的属性都包括在dfm文献中,而delphi在编写翻译步调的功夫,运用编写翻译训令{$r *.dfm}仍旧把dfm文献消息编写翻译到可实行文献中。所以,不妨确定创造窗体的功夫须要去读取dfm消息,用什么去读呢,固然是treader了!
经过对步调的一步步的盯梢,不妨创造步调在创造窗体的进程中挪用了treader的readrootcomponent本领。该本领的效率是读出根组件及其所具有的十足组件。来看一下该本领的实行:
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开始挪用readsignature读取filer东西标签(’tpf0’)。载入东西之前检验和测定标签,能提防大略大概,引导读取失效或落伍的数据。
再看一下readprefix(flags, i)这一句,readprefix本领的功效与readsignature的很相象,只然而它是读取流中组件前方的标记(prefix)。当一个write东西将组件写入流中时,它在组件前方预写了两个值,第一个值是指明组件能否是从前辈窗体中接受的窗体和它在窗体中的场所能否要害的标记;第二个值指明它在前辈窗体创造步骤。
而后,即使root参数为nil,则用readstr读出的类名创造新组件,并从流中读出组件的name属性;要不,忽视类名,并确定name属性的独一性。
froot.readstate(self);
这是很要害的一句,readstate本领读取根组件的属性和其具有的组件。这个readstate本领固然是tcomponent的本领,但进一步的盯梢就不妨创造,它本质上最后仍旧定位到了treader的readdatainner本领,该本领的实行如次:
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;
个中有如许的这一条龙代码:
while not endoflist do readproperty(instance);
这是用来读取根组件的属性的,对于属性,前方提到过,既有组件自己的published属性,也有非published属性,比方ttimer的left和top。对于这两种各别的属性,该当有两种各别的读本领,为了考证这个办法,咱们来看一下readproperty本领的实行。
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;
为了俭朴篇幅,简略了少许代码,这边证明一下:fpropname是从文献读取到的属性名。
propinfo := getpropinfo(instance.classinfo, fpropname);
这一句代码是赢得published属性fpropname的消息。从接下来的代码中不妨看到,即使属性消息不为空,就经过readpropvalue本领读取属性值,而readpropvalue本领是经过rtti因变量来读取属性值的,这边不复精细引见。即使属性消息为空,证明属性fpropname为非published的,它就必需经过其余一种体制去读取。这即是前方提到的defineproperties本领,如次:
instance.defineproperties(self);
该本领本质上挪用的是treader的defineproperty本领:
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;
它先去比拟读取的属性名能否和预设的属性名沟通,即使沟通而且读本领readdata不为空时就挪用readdata本领读取属性值。
好了,根组件仍旧读上去了,接下来该当是读该根组件所具有的组件了。再来看本领:
procedure treader.readdatainner(instance: tcomponent);
该本领反面有一句如许的代码:
while not endoflist do readcomponent(nil);
这恰是用来读取子组件的。子组件的读取体制是和上头所引见的根组件的读取一律的,这是一个树的深度遍历。
到这边为止,组件的读体制仍旧引见结束。
再来看组件的写体制。当咱们在窗体上增添一个组件时,它的关系的属性就会生存在dfm文献中,这个进程即是由twriter来实行的。
Ø twriter
twriter 东西是可范例化的往流中写数据的filer东西。twriter东西径直从tfiler接受而来,除去掩盖从tfiler接受的本领外,还减少了洪量的对于写百般数据典型(如integer、string和component等)的本领。
twriter东西供给了很多往流中写百般典型数据的本领, twrite东西往流中写数据是按照各别的数据采用各别的方法的。 所以要控制twriter东西的实行和运用本领,必需领会writer东西保存数据的方法。
开始要证明的是,每个filer东西的流中都包括有filer东西标签。该标签占四个字节其值为“tpf0”。filer东西为writesignature和readsignature本领存取该标签。该标签重要用来reader东西读数据(组件等)时,引导读操纵。
其次,writer东西在保存数据前都要留一个字节的标记位,以指出反面寄存的是什么典型的数据。该字节为tvaluetype典型的值。tvaluetype是列举典型,占一个字节空间,其设置如次:
tvaluetype = (vanull, valist, vaint8, vaint16, vaint32, vaentended, vastring, vaident,
vafalse, vatrue, vabinary, vaset, valstring, vanil, vacollection);
所以,对writer东西的每一个写数据本领,在实行上,都要先写标记位再写相映的数据;而reader东西的每一个读数据本领都要先读标记位举行确定,即使适合师从数据,要不爆发一个读数据失效的特殊事变。valist标记有着特出的用处,它是用来标识反面将有延续串典型沟通的名目,而标识贯串名目中断的标记是vanull。所以,在writer东西写贯串几何个沟通名目时,先用writelistbegin写入valist标记,写完数据名目后,再写出vanull标记;而读那些数据时,以readlistbegin发端,readlistend中断,中央用endoflist因变量确定能否有vanull标记。
来看一下twriter的一个特殊要害的本领writedata:
procedure twriter.writedata(instance: tcomponent);
……
begin
……
writeprefix(flags, fchildpos);
if usequalifiednames then
writestr(gettypedata(ptypeinfo(instance.classtype.classinfo)).unitname + '.' + instance.classname)
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;
从writedata本领中咱们不妨看出天生dfm文献消息的概貌。先写入组件前方的标记(prefix),而后写入类名、范例名。紧接着有如许的一条语句:
writeproperties(instance);
这是用来写组件的属性的。前方提到过,在dfm文献中,既有published属性,又有非published属性,这两种属性的写入本领该当是不一律的。来看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;
请看底下的代码:
if isstoredprop(instance, propinfo) then
writeproperty(instance, propinfo);
因变量isstoredprop经过保存控制符来确定该属性能否须要生存,如需生存,就挪用writeproperty来生存属性,而writeproperty是经过一系列的rtti因变量来实行的。
published属性生存完后就要生存非published属性了,这是经过这句代码实行的:
instance.defineproperties(self);
defineproperties的实行前方仍旧讲过了,ttimer的left、top属性即是经过它来生存的。
好,到暂时为止还生存如许的一个疑义:根组件所具有的子组件是如何生存的?再来看writedata本领(该本领在前方提到过):
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;
ignorechildren属性使一个writer东西保存组件时不妨不保存该组件具有的子组件。即使ignorechildren属性为true,则writer东西保存组件时不存它具有的子组件。要不就要保存子组件。
instance.getchildren(writecomponent, froot);
这是写子组件的最要害的一句,它把writecomponent本领动作回调因变量,依照深度优先遍历树的规则,即使根组件froot生存子组件,则用writecomponent来生存它的子组件。如许咱们在dfm文献中看到的是树状的组件构造。