• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

以DelphiPackage架构的应用程序开发

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

■印象中的Package

 

在一般的AP开发时,我们知道在Delphi7.0整合环境中将Project->Options->选到Packages卷标页,Builder with runtime packages选项打勾,就会让编译出来的执行文件Size变小很多(以空白的Form1为例,编译出来的Size367kb变成20kb),因为它把一些VCL共享模块的Loding放到*.bpl中;句话这个变小的EXE文件在执行时是需要那些*.bpl的,而原本较大的执行文件执行时则不需要那些*.bpl,这样看来其实只是汤不药罢了。

这种编译架构的差异有什么优点?试想,如果Project1.exe&Project2.exe都不使用Builder with runtime packages选项,Project1.exe跟Project2.exe编译出来的Size都是367kb,若有10个ProjectN.exe,则占用的Size将是全部exe加起来的总和,若是我们将其中共享的部分(如VCL共享模块)独立出来,每个EXE都将只有20K(以测试用的空白Form而言),缺点则只有每个exe执行时都要在系统搜寻路径中找得到那些VCL的共享模块(以*.bpl文件名存在);所以这种架构适用于一个项目系统内有多个独立执行文件或独立业务模块存在的情况,多个执行文件就可以分散给多个人去开发,也可依照应用程序不同的业务性质去区隔并分别设计。

将执行文件分散开来开发,这种做法跟一个主执行文件配合多个不同业务别的DLL开发没有什么差别,而降低每支执行文件的Size也只是Package最普通的应用;使用Package有一个更强大的优点——可以共享变量,如果将数据模块当成一个共享变量的观念去看待,我们不用在每次启动或关闭不同的子系统业务模块时,重新连接或释放数据库的Connection。不过,要真正能共享数据库模块是需要每个业务模块皆以*.bpl的方式存在,这也是实际应用上的状况。

项目架构种类

Package编译方式

类型一

Project0.exe(MIS主系统)

Project1.exe(会计子系统)

Project2.exe(人事子系统)

Project3.exe(库存子系统)

所有EXE文件编译选项

Builder with runtime

packages

1.每个子系统都是完整独立的EXE文件;每个EXE文件至少都有数百KB以上

类型二

Project0.exe(MIS主系统)

Project1.exe(会计子系统)

Project2.exe(人事子系统)

Project3.exe(库存子系统)

VCL共享模块(*.bpl)

所有EXE文件编译选项

Builder with runtime

packages(打勾)

1.每个子系统虽然都是EXE文件,但执行时需要VCL的共享模块存在;此种方式,每个EXE文件SIZE都只有几十KB

2.此种架构只有节省档案SIZE的优点

类型三

Project0.exe(MIS主系统)

Project1.bpl(会计子系统)

Project2.bpl(人事子系统)

Project3.bpl(库存子系统)

VCL共享模块(*.bpl)

DataMoudle(db.bpl)

所有EXE文件编译选项

Builder with runtime

packages(打勾)

1.将各子系统中共享的程序(如连接数据库的模块)独立出来共享

2.除主系统外其余子系统皆为BPL型式存在

3.节省档案SIZE外,并有共

用数据库连结模块的优点

注:

1.MIS主系统(Project0.exe)可能只是一个选单(Menu),用来当作启动各子系统的Shell

2.VCL共享模块(*.bpl)Delphi在安装时即安装至C:"WINNT"SYSTEM32"中的那些*.bpl档案,不管你EXE文件是否Builderwithruntimepackages,那些*.bpl早已存在,端看你要不要用它而已(Builderwithruntimepackages是否打勾)

3.当AP开发是以Builderwithruntimepackages(打勾)编译时,RELEASE到客户端同时也要将C:"WINNT"SYSTEM32"中的那些*.bpl档案COPY到客户端,因为客户端没有安装Delphi,所以没有那些VCL共享模块(*.bpl)

4.BPL跟EXE一样是可以“LOAD”其它的*.bpl;如Project1.bpl(会计子系统)会”LOAD”VCL共享模块(*.bpl)以及DataMoudle(db.bpl)

5.在类型三中,作为唯一的EXE文件,Project0.exe编译时一定要Builderwithruntimepackages(打勾),否则在动态加载其它子系统模块时,会出现找不到类别的错误

 

Package架构的优缺点

 

使用Package的优点:

1.类似DLL-应用程序可以被高度的模块化

2.优于DLL之处-可以共享变量

使用Package的缺点:

1.架构较复杂,需要花比较多的心思在模块化的设计

2.需要程度较高的开发人员,若对package的使用不熟,容易出trouble

 

■哪些东西可以开发成为Package

 

前面讲过,Package其实就是类似DLL的一种架构,但是是专门属于Borland C++Builder/Delphi使用的一种DLL架构,举凡有含Form的Unit文件、无Form的Unit文件(如自己写的函数库)、组件等等,都可以编译成为Package

 

Package档案的类型

 

Package项目

相当于一般项目

相当于DLL项目

编译前

*.DPK

Package项目文件

DPR文件

DPR文件

*.PAS

PackageSource

PAS文件(Source)

PAS文件(Source)

编译后(产出)

*.DCP

Package产出文件

 

LIB文件

*.BPL

Package产出文件

EXE文件

DLL文件

Package中的DCP文件,跟DLL项目的LIB文件类似,DCP是提供为静态连结编译之用(注意:只是静态连结的“编译过程”用之),BPL则是直接提供为执行环境之用;DCP只有在提供给别的EXE或BPL静态连结的“编译过程”中会用到,所以不用RELEASE到客户端;在执行环境中,Package不管是被静态联结或是动态连结使用,一律以BPL型式存在。

例如Project0.exe以“静态连结”方式使用到Package1,Project0.exe在编译时需要Package1.dcp的档案存在,但编译后分发到客户端的档案则为roject0.exe以及Package1.bpl(Package1.dcp不用release),虽然它们之间是使用静态连结(加载)…

如果Project0.exe以“动态连结”方式使用到Package1,Project0在编译过程中根本不需要任何Package1的档案,而分发到客户端一样仍然只有roject0.exe以及Package1.bpl。

 

Package的载入(EXE载入BPL的角度来看)

 

静态加载–不管用得用不到,该*.dcp是一定都要加载的,也就是在exe项目选项中的Builder with runtime packages->Add加入,如Project0.exe(MIS主系统)一定要加载VCL共享模块(*.dcp),否则无法产生GUI接口;所以就用静态加载的方式加载它;静态加载的*.dcp,是在编译时就已决定的

动态加载–使用到时,才透过LoadPackage()这个API来呼叫,如Project0.exe(MIS主系统)呼叫各个子系统的*.bpl,是透过Coding的方式去加载子系统的*.bpl,Project0.exe(MIS主系统)并不需要在编译时将所有子系统加载编译

 

■Package的载入(从BPL载入BPL的角度来看)

 

静态加载–放在Package项目之Requires区段中的*.dcp文件,反正是“一定要用到”的,不需要利用动态加载的就放在这儿

动态加载–跟EXE动态加载BPL方式相同,使用到时,才透过Coding方式利用LoadPackage()这个API来加载的

 

■Package项目的建立

 

在Delphi7.0整合环境中,主菜单->File->Close ALL关掉所有的Form及项目;然后再一次:主菜单->File->New->Other...->选择Package,建立一个全新的Package项目

一个全新的Package应该是长的如下图所示,分为两个部分:Contains内放的是项目的主题,也就是我们要为这个Package开发的程序代码;Require内放的是要“静态连结” 的其它Package(*.dcp文件),也就是不需要利用LoadPackage()这个API来加载(动态加载)的Package就放到Require中;rtl.dcp是系统内建就已Require的Package,应该是VCL之类的东西吧

接下来我们要开始设计项目内容,帮这个Package项目加入一个空白的Form,到Delphi主菜单->File->New->Form;我们再回到Project Manager看看在Contains区段是否多了Unit1(Form1);前面说过,Contains内放的是项目的主题,也就是我们要为这个Package开发的程序代码,接下来要在Form1中放什么组件,写什么程序代码,都跟一般的Form没什么两样,只是若这个Form是写在一般的项目中(Project1.dpr)它可以被编译成EXE文件,而在Package项目中(Package1.dpk),它只能被编译成Package1.dcp(给别的项目静态连结编译时用)和Package1.bpl

Package1.dcp以及Package1.bpl这两个编译后的结果,Default是放到Delphi7.0目录的."Projects"Bpl"中;若要改变,请至Delphi主菜单->Project->Options(如下图),修改项目的输出目录路径为目前路径(.")

还有,很重要的一点,在Package项目中的Form由于将来会被别人加载使用(不管被静态或被动态加载);它都需要先向系统注册它的类别;所以在前面的例子中,将Unit1(Form1)开发好后,最后要在end.之前插入注册(RegisterClass)/注销(UnRegisterClass)的程序代码,注册内容就是自己的Form类别(如TForm1、TForm2等等)

(其它程序代码)

......

......

initialization

RegisterClass(TForm1);

finalization

UnRegisterClass(TForm1);

end.

 

■Package的应用限制

 

如果只是一个EXE文件附带一个BPL文件,这种架构还算单纯,但如果如文章开始所述:一个MIS主系统(Project0.exe)带着多个子系统(*.bpl),那会有什么限制发生呢?

1.各个Package(*.bpl)在开发过程中,彼此的Contains区段中不能有同名的Unit

2.共享的unit一定要放在package,也就是要把共享模块变成Package

我们现在来想想,如果是我们来主导这个系统,我们会如何设计呢?

1.虽然各项子系统是各自独立开发,甚至是交由不同的开发TEAM来完成,但为了接口的风格一致及操作统一(如Button的大小及位置),我们会有一个共通的BaseForm的雏形,让所有的子系统的主Form都由这个BaseForm继承而来,这样会让子系统(Package)的Contains区段都会有一个共同uses的BaseForm.pas

2.为了程序代码的一致性,也为了增加Coding速度,公司累积了程序代码经验,可能会有一个公用副函数集MySub供各个子系统呼叫,这样也会让子系统(Package)的Contains区段都会有一个共同uses的MySub.pas

为了不让BaseForm.pas及MySub.pas成为Package开发的限制瓶颈,所以我们要将BaseForm及MySub也变成Package(成为BaseForm.dcp及MySub.dcp),然后让各个子系统Package放在Requires中静态连结编译

 

■Package的动态加载

 

前面已介绍了Package在静态加载以及编译的方法,现在要介绍Package应用的重头戏-动态加载;一般来说,共通的副函数或共享分享的数据库模块以及共享的继承样板会先被制作成Package,然后被主程序(EXE文件)及各个子系统模块(BPL文件)作为静态连结之用;而整个系统开发的主角,也就是各个子系统模块会被主程序或子系统模块(BPL文件)间,当成动态加载的目标。

var

ModuleInstance1:HMODULE;

{$R*.dfm}

//---------------------------------------------------------------

//动态加载Package

//---------------------------------------------------------------

procedureTForm0.Button1Click(Sender:TObject);

begin

ModuleInstance1:=LoadPackage('Package1.bpl');

end;

//---------------------------------------------------------------

//Package中的Form1带出

//---------------------------------------------------------------

procedureTForm0.Button2Click(Sender:TObject);

var

frm : TcustomForm;

begin

frm :=CreateFormByClassName('TForm1');

try

frm.ShowModal;

finally

frm.Release;

end;

end;

//---------------------------------------------------------------

//释放Package

//---------------------------------------------------------------

procedureTForm0.Button3Click(Sender:TObject);

begin

UnloadAddInPackage(ModuleInstance1);

end;

注:

1.在上列的程序代码中,加载PackageLoadPackage()是系统建函数

2.带出Package中的Form资源或是释放Package的函数由于要多做一些处理,所以把它包在CreateFormByClassName()以及UnloadAddInPackage()两个自订函数中

//---------------------------------------------------------------

//自订函数CreateFormByClassName(),建立Form

//---------------------------------------------------------------

Function TForm0.CreateFormByClassName(const ClassName:string) : TCustomForm;

var

AClass:TPersistentClass;

begin

AClass:=GetClass(ClassName);

If AClass=nil then exit;

Result:=TComponentClass(AClass).Create(Application) as TCustomForm;

//Result:=TCustomForm(TComponentClass(AClass).Create(Application));

end;

//---------------------------------------------------------------

//自订函数CreateDataModuleByClassName(),建立数据模块

//---------------------------------------------------------------

Function TForm0.CreateDataModuleByClassName(const ClassName: string):TDataModule;

var

AClass:TPersistentClass;

begin

Result:=nil;

AClass:=GetClass(ClassName);

If AClass=nil then exit;

Result:=TComponentClass(AClass).Create(Application) as TDataModule;

end;

//---------------------------------------------------------------

//自订函数UnloadAddInPackage(),释放Package

//---------------------------------------------------------------

Procedure TForm0.UnloadAddInPackage(ModuleInstance:HMODULE);

var

i:Integer;

M:TMemoryBasicInformation;

begin

for i:=Application.ComponentCount-1 downto 0 do

begin

VirtualQuery(GetClass(Application.Components[i].ClassName),M,SizeOf(M));

if (ModuleInstance=0) or (HMODULE(M.AllocationBase)=ModuleInstance) then

Application.Components[i].Free;

end;

//下面这两个函数应该是只要取其中一个呼叫即可

UnRegisterModuleClasses(ModuleInstance);//直接注销Package

UnloadPackage(ModuleInstance);//间接注销,呼叫Package中的finalization区段

end;

 

■完整的Package项目架构范例

 

子系统名称

部主要对象

补充

Project0.exe(MIS主系统)

Form0(Unit0.pas)

整个项目中唯一的EXE文件ProjectOption选项要设


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Delphi10.3状态栏上显示进度条/图片发布时间:2022-07-18
下一篇:
Delphi内存操作API函数(备查,并一一学习)发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap