package(转)

来源:百度文库 编辑:神马文学网 时间:2024/04/28 09:48:52
不知道各位用Delphi写数据库程序的朋友有没有碰到过这样的问题:写出来的
程序体积太庞大!我写的上一个项目中可执行文件竟然达到4.3M!很可怕的体积!
这样不仅分发程序比较困难,而且维护也很难:程序给客户后发现界面上某个标签
的字写错了,然后不得不把这样一个巨无霸重新编译,重新发给客户。
想到分割程序,用什么呢?COM/MTS?在小项目中太得不偿失了!用Dll?如果
是做非数据库程序还可以,如果做数据库程序就有麻烦了:每个Dll都会在自己独立
的对话中和数据库连接,造成资源的极大浪费,而且还有全局变量的问题。在当我
快绝望的时候看到了李维的一篇有关package的文章(关心package的朋友应该都看
过那篇文章),但那篇文章里写的不是很清楚,看了还有些不明白。大富翁上也有
很多朋友讨论,但都比较零碎,上一段时间结合网上查到的文章,还有自己一些摸索,
终于基本上搞清楚了package的一些用法,现在贴出来和大家交流。

package的使用和dll类似,有静态和动态调用两种方法。

我们用一个简单的数据库程序来说明,假设工程组成为:

ClassMgr.dpr--------------------工程文件
uGlobal.pas---------------------全局变量单元
frmDM.dfm(uDM.pas)--------------数据模块
frmMain.dfm(uMain.pas)----------主窗体
frmStudent.dfm(uStudent.pas)----学生档案窗体
frmScroe.dfm(uScore.pas)--------成绩输入窗体
frmQuery.dfm(uQuery.pas)--------成绩查询窗体

这里面的一些uses关系就不说了,大家应该都很清楚吧!

1、静态方法

现在的目标是把每个功能窗体放进一个包中,以后当需要修改相应的模块时只要发
布相应的包即可。为了达到这个目的,我们需要添加四个包:

basic.dpk
Contains: uDM.pas; uGlobal.pas
student.dpk
Contains: uStudent.pas
scroe.dpk
Contains: uScord.pas
query.dpk
Contains: uQuery.pas

因为所有的数据窗体都需要引用数据模块(uDM.pas)和全局变量(uGlobal.pas)单元,
所以我们把uGlobal.pas和uDM.pas放进basic.dpk中,至于为什么这么做我们等一下
再说。

在ClassMgr.dpr的Options中修改属性,使它Build with runtime packages,
package的列表为:
basic.bpl
student.bpl
scroe.bpl
query.bpl

另外,在student.dpk, scroe.dpk, query.dpk的Requires中添加basic.bpl;
basic.dpk的Requires中添加
vcl50.bpl
vcldb50.bpl
vclado50.bpl(假设用ADO连接)

接下来就是编译包和可执行文件,这里需要注意编译的顺序,正确的编译顺序应该为:
basic.bpl -> (student.dpk, scrod.dpk, query.dpk) -> Classmgr.dpr

编译完后看一下,classmgr.exe的体积是不是小很多了?差不多只用100k左右,因为
其实里面只包含了主窗体。每个bpl文件的大小也差不多100k左右(具体视代码规模)。

[red][b]这里需要额外说明的是:[/b][/red]
在主窗体frmMain中也要引用uDM, uGlobal等单元,但是因为已经Build with相应
的包了,所以Delphi只会编译相应的声明,而代码的实现部分在相应的包里。

2、动态方法

我们还是拿上面的例子

在这之前需要将每个窗体单元(主窗体除外)作一些修改:
即在每个窗体单元的Initialization部分注册相应的窗体类。
代码例子如下:
initialization
RegisterClass(TfrmStudent);
finalization
UnRegisterClass(TfrmStudent);
end.

然后在主窗体需要调用该窗体的地方改成下面的方式:
procedure TfrmMain.btStudentClick(Sender: TObject);
var
h: HMODULE;
frmStudent: TForm;
begin
try
h := LoadPackage('student.bpl');
frmStudent := TForm(TComponentClass(FindClass('TfrmStudent')).Create(Application));
frmStudent.ShowModal;
finally
frmStudent.Free;
UnLoadPackage(h);
end;
end;
在这里先载入包student.bpl, 然后取得我们在包里注册的TfrmStudent类,
因为FindClass返回的值是TPersistentClass类型,需要强制转换为TComponentClass类
后创建相应的实例。窗体释放后再释放student.bpl

在这种方式下就不需要在frmMain引用其他的单元了(全局变量单元除外)。需要补充
一点的是,在这种方式下,工程classmgr.dpr也需要Build with runtime package,
否则会在FindClass时无法取得相应的类指针。只是student.bpl等包不需要在package
列表中,只需添加basic.bpl即可。

现在说说为什么要将uDM.pas和uGlobal.pas加入到basic.bpl包中。
大家都知道win32模式下Dll不能直接和主程序共享全局变量,package的高明之处
就在于package和主程序能共享全局变量,所要作的工作只是把相应的全局变量放到某
个包中,然后引用该全局变量的包和主程序只要引用该包即可。因为在这个程序中
uGlobal.pas中存放的是全局变量,主程序和其他包都需要引用这些全局变量,故将它
加入basic.bpl中,uDM中的数据模块也是一个全局窗体变量,也需要加入basic.bpl中。

所以在其他包的Requires中都有basic.bpl

3、总结

================= 静态调用模式 =====================

一些关系:

classmgr.dpr
Build with runtime package: student.bpl, score.bpl, query.bpl, basic.bpl

uMain.pas中:
uses
uGlobal, uStudent, uScore, uQuery;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^不能删除

student.bpl
requires: basic.bpl

score.bpl
requires: basic.bpl

query.bpl
requires: basic.bpl

basic.bpl
requires: vcl50.bpl, vcldb50.bpl, vclado50.bpl


================= 动态调用模式 =====================

一些关系:

classmgr.dpr
Build with runtime package: basic.bpl
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^一定要

uMain.pas中:
uses
uGlogal, uStudent, uScore, uQuery;
^^^^^^^^^^^^^^^^^^^^^^^^^这几个单元不需要,但uGlobal仍需要

student.bpl
requires: basic.bpl

score.bpl
requires: basic.bpl

query.bpl
requires: basic.bpl

basic.bpl
requires: vcl50.bpl, vcldb50.bpl, vclado50.bpl

================= 两种模式的比较 =====================

理论上讲动态调用方式下会比较节省资源,因为相应的包只在需要的时候才
载入内存,但需要牺牲速度为代价。
但在实际使用中,项目窗体可能很多,包也可能有很多,频繁的载入包可能
会使开发者陷入一个比较混乱的状态,可能有的包载入了而没有释放掉,而且包
在什么时候释放也需要很好的控制,这样就不能达到节省资源的目的,相反却增
加了编程的复杂性和牺牲了速度。
另外,动态调用还有一个很明显的缺点是:调用时无法直接知道所取得的某
个类是否有某个方法或属性。
比如我们上面的例子中的frmStudent窗体有一个公有方法:GetStdInfo;
在动态调用时:
var
frmStudent: TForm;
begin
h := LoadPackage('student.bpl');
frmStudent := TForm(TComponentClass(FindClass('TfrmStudent')).Create(Application));
frmStudent.ShowModal;

这里我们就不能写入frmStudent.GetStdInfo这样的代码,因为事实上frmStudent
只是TForm类的一个实例,没有GetStdInfo这样的方法。在这方面动态package和COM有
点类似,但使用COM时编译器允许使用Variant方式来调用一个未知的方法,但Package
却不行。在静态调用时就不存在这种问题,因为代码的实现方式和不使用包时完全一样。

一点补充说明:不能在包之间交叉包含(Contains)某个单元,比如:在basic.bpl中已经
包含了uDM.pas单元,在student.bpl中就不能再包含uDM.pas这个单元,否则编译是不能
通过。

4、建议

我个人的意见是使用静态调用。但如果项目很大,package也就失去了她优势了,
到那个时候我想COM /MTS会是一个比较好的选择