Delphi/Free pascal 终极流化技术

来源:互联网 发布:vscode mac 下载 编辑:程序博客网 时间:2024/04/28 12:33
流化技术,有些编程语言中也称为序列化技术,亦即把一个对象的状态保存到一个文件中,也可以逆向从文件中读取内容以初始化这个对象。
 
Object Pascal语法提供不完全流化的能力,仅支持对Published属性的流化/反流化操作,对private、public及protected属性则无能为力。
 
在Delphi/Free pascal的VCL类库体系中,处于金字塔次顶层的TPersistent封装了基本的流化能力,按照Delphi帮助文档说明,TPersistent类的基本功能是提供Assign方法和流化能力。
 
TPersistent encapsulates the behavior common to all objects that can be assigned to other objects, and that can read and write their properties to and from a form file (.xfm or .dfm file)。
 
事实上,包括VCL源码,我们在使用Delphi的流化技术,都是从TComponent类开始的,Delphi帮助文档中提供了ComponentToString、StringToComponent两个方法供我们日常编程中进行简单的流化、反流化操作。
 
从ComponentToString、StringToComponent两函数的测试结果中,我们可以发现ComponentToString函数过于简单,有一个致命的缺点,不支持嵌套对象的流化。在实际Delphi编程中,尤其是和界面相关的,对象嵌套的情况可以说是家常便饭,比如在一个TPanel上放置一个TButton,可以说整个窗口就是一个嵌套的TForm类。当然对于TForm类,Delphi IDE以特殊的复杂方式提供了针对TForm的嵌套流化/反流化能力,但是Delphi并未提供非TForm类的嵌套流化能力。因此我们需要探索如何实现非TForm类的嵌套对象流化,在实际编程中这项高级技术也很实用。
 
比如,我们有一个窗口FormA,上面有一个表格GridA,里面再嵌套了TColumn、TTitle、TFooter等其它对象,TColumn、TTitle和TFooter等对象的放置组合决定了GridA的显示样式。此时,我有其它的窗口FormB,FormC,由于设计原因,并未从FormA继承,但是我希望在这些窗口上面显示一个和FormA上的GridA完全一样的表格,一般程序员的方法是在FormB和FormC的pas和dfm文件中完整复制一份GridA的类代码申明和资源申明,但这就造成了和编程语言一样的代码重复问题,如果我的FormA上的GridA经常要更改、维护,那不得不重新在FormB、FormC中重新拷贝复制GridA的类代码申明和资源申明,这是一个很大的工作量。
 
因此我们需要“界面”重构,去掉重复的“界面”设计,使得窗口界面清爽、简洁,并使得相关代码具有高度的灵活性和适应性。
 
为了实现嵌套对象的流化能力,我们需要不断地反复递归流化嵌套对象中的各个元素,因此我们实现了一个支持递归流化的函数function RecurComponentToString(var s:string;AControl: TWinControl):string,此函数须间接调用Delphi提供的ComponentToString函数。
 
流化示例:
 
var
       s:string;
begin
RecurComponentToString(s, Panel1);
Memo1.Lines.Text:=s;
       end;
反流化示例:
var
  pal:TPanel;
begin
  RegisterClasses([TPanel,TListBox,TComboBox,TScrollBar]);//所有的控件类必须注册
  pal:=TPanel(StringToComponent(Memo1.Lines.Text));
  if (pal<>Nil) then
  begin
    pal.Parent:=self;
    pal.Left:=10;
    pal.Top:=10;
    pal.Visible:=true;
  end;
end;
 
使用RecurComponentToString与StringToComponent函数就可实现嵌套对象的完美流化和反流化操作了。但是在使用时还是存在一个问题,流化时源对象必须已经创建并存在实例,也许读者会认为在其它语言,比如JAVA中也必须如此,但我认为这种情况仍然不够方便,而且创建对象既浪费时间也浪费内存,其实很多情况下在Delphi中是可以不必要创建对象的实例而直接进行流化/反流化的。诀窍就在于Delphi已经把所有的窗口资源字符串都放在Exe文件中了,从Exe文件的资源信息中读取相关信息,即可直接使用反流化技术从TFormA类动态克隆一个GridA到FormB中,而TFormA的实例FormA根本就可以不用创建。
 
针对这个问题,下面我首先介绍一种比较简单的解决方法。
 
Dephi的classes.pas类有一个ReadComponentRes方法,可以直接将单个未嵌套的对象进行反流化操作,我们就仿造这个方法进行改造,以实现我们的目的。首先新建一个单元StreamExpert.pas,为避免和classes.pas中的ReadComponentRes冲突,再写一个ReadComponentResExpert方法,实现和classes.pas中的ReadComponentRes完全一样。
 
function ReadComponentResExpert(const ResName: string;
  SubFormName: string; Instance: TComponent): TComponent;
var
  HInstance: THandle;
begin
  if Instance <> nil then
    HInstance := FindResourceHInstance(FindClassHInstance(Instance.ClassType))
  else
    HInstance := 0;
  if InternalReadComponentRes(ResName, SubFormName, HInstance, Instance) then
    Result := Instance
  else
    raise EResNotFound.CreateFmt(SResNotFound, [ResName]);
end;
 
接着我们实现自己的InternalReadComponentRes(const ResName: string; SubFormName: string; HInst: THandle; var Instance: TComponent):方法,InternalReadComponentRes工作过程如下:首先根据Form名称从EXE资源中读取整个Form的资源字符串,然后根据SubFormName参数使用ParseSubFormStr抽取某个控件的资源字符串,再使用Delphi的StreamReadComponent对控件实行反流化操作。
 
InternalReadComponentRes相对于StringToComponent更复杂,支持从基类inherited下来的资源的反流化,StringToComponent则不具备该能力,因此该函数适合对窗口资源字符串进行反流化操作。InternalReadComponentRes首先按照窗口类名从Exe中读取资源字符串,接着再调用ParseSubFormStr处理,发现有否嵌套或继承对象,若有,则直接递归当前函数。在接下来的程序流程中,和原来classes.pas中InternalReadComponentRes一样读取对象的属性。如此以来,即解决了嵌套继承对象的反流化问题。
 
对于对象的事件问题,因为必需和代码相关联,处理比较复杂,本例为了简单处理,直接使用FilterEventStr函数过滤掉,如果实在需要实现事件的反流化,可以先在一个类中先编写好函数过程名相同的事件代码,使用SetMethodProp和Tobject.MethodAddress来将该事件挂勾上。
 
ReadComponentResExpert使用示例:
 
  cxGridTmp:=TcxGrid.Create(self);
  try
    cxGridTmp:=TcxGrid(ReadComponentResExpert('TFrmSiteMoni', 'cxGrid1',cxGridTmp));
    cxGridTmp.Update;
  except
  end;
  cxGridTmp.Parent:=self;
cxGridTmp.Visible:=true;
 
TFrmSiteMoni为所欲克隆源窗口的TForm类,cxGrid1为欲克隆的源窗口上的控件名称,cxGridTmp为反流化出来的目标控件,可以直接嵌在当前界面中。
 
由于篇幅关系,本文未能给出RecurComponentToString、InternalReadComponentRes、ParseSubFormStr、FilterEventStr等函数的详细源码、读者欲索取这几个函数的完整源码请联系 superyys@163.com。
 
以上这种解决方案是借助于字符串解析来实现,由于是自己写的一个比较简单粗糙的字符串处理方法,只能处理标准dfm文件中的资源格式,容错性和兼容性较差。如果想更完善的处理嵌套继承对象的流化/反流化问题,并完美支持事件的反流化,还得自己继承TPersistent、TFiler、TReader、TWriter等大量的VCL基本类,笔者尝试编写了一classex.pas,封装了相关的类并实现了嵌套继承对象的较为完善的流化/反流化技术,由于实现机制复杂和篇幅原因,不在此叙述,有兴趣者可直接联系 superyys@163.com 共同探索研究。
原创粉丝点击