Delphi中的动态包


======================================================
注:本文源代码点此下载
======================================================

delphi中的动态包

为什么要使用包?

答案很简单:因为包的功能强大。设计期包(design-timepackage)简化了自定义组件的发布和安装;而运行期包(run-time package) 则更是给传统的程序设计注入了新鲜的力量。一旦把可重用的代码编译为运行期库中,你就可以在多个应用程序中共享它们。所有应用程序都可以通过包访问标准组 件,delphi自己就是这么干的。因为应用程序不必在可执行文件中单独复制一份组件库,这样就大 大节省了系统资源和磁盘空间。此外,包还可以减少花费在编译上的时间,因为你只需编译应用程序特有的代码。

如果可以动态的使用包,那么我们还可以获得更多的好处。包提供了一种新颖的模块化方法来开发应用程序。有些时候你也 许想把某些模块作为应用程序的可选部件,例如一个记帐系统附带一个可选的hr模块。某些情况下,你 只需安装基本的应用程序,而在另外一些情况下你就可能需要额外安装hr模块。这种模块化的方法可以 通过包技术很容易的实现。在过去,这只能通过动态装载dll实现,但是使用delphi的包技术,你就可以把应用程序的各个模块类型分别打“包”成捆。特别是从包中创建的类对象则属于应 用程序所有,因此可以与应用程序中的对象交互。

运行期包与应用程序

许多开发者只把delphi包看作放组件的地方,事实上包可 以(而且也应该)应用于模块化应用程序设计。

为了演示如何用包来模块化你的应用程序,我们创建一个例子:

1、 新建一个具有两个窗体的delphi程序:form1和form2;

2、 将form2从自动创建窗体列表中移除(project |options | forms);

3、 在form1上放一个按钮,并且在按钮的onclick事件处理器中输入如下代码:

with tform2.create(application) do

begin

showmodal;

free;

end;

4、记住添加unit2到unit1的uses子句中;

5、 保存并运行工程。

我们创建了一个简单的应用程序,它显示一个带按钮的窗体,点击这个按钮则会创建并显示出另一个窗体。

但是如果想将上述例子中的form2包含在一个可重用模块 中,并使它依然可以正常工作,我们该怎么办呢?

答案是:包!

要为form2创建包需要以下工作:

1、 打开工程管理器(view | project manager);

2 、右击project group,选择“add newproject...”;

3、在“new”项目列表中选择“package”;

4、 现在你应该可以见到包编辑器;

5、选择“contains”项目,然后点击“add”按钮;

6、 然后点击“browse...”按钮,并选择“unit2.pas”;

7、现在包中应该包含了“unit2.pas”单元;

8、 最后保存并编译包。

现在我们完成了这个包。在你的project\bpl目录中 应该有一个名叫“package1.bpl”的文件。(bpl是borland package library的缩写,dcp是delphi compiledpackage 的缩写。)

这个包已经完成了。现在我们需要打开包选项开关

并重新编译原先的应用程序。

1、 在工程管理器中双击“project1.exe”以选中 该工程;

2、 右击并选择“options...”(你也可以从菜单中 选择project | options...);

3、 选中“packages”选项页;

4、 选中“build with runtime packages” 检查框;

5、 编辑“runtime packages”编辑框:“vcl50;package1”,并点击“ok”按钮;

6、 注意:不要从应用程序中移除unit2;

7、 保存并运行应用程序。

应用程序会象从前一样运行,不过区别可以从文件的大小上看出来。

project1.exe现在只有14k大 小,而从前则是293k。如果你用资源浏览器查看exe和bpl文件的内容,你就会发现form2的dfm和代码现在都保存在包中。

delphi在编译期完成对包的静态连接。(这就是为什么你不能从exe工 程中移除unit2。)

想想你可以由此得到什么:你可以在包中创建一个数据访问模块,并且在更改数据访问规则时(比如从bde连接转为ado连接),稍作修改并重新发布这个 包。或者,你可以在某个包中创建一个显示“此选项在当前版本中不可用”信息的窗体,然后在另一个同名的包中创建一个具有完整功能的窗体。现在我们不费吹灰 之力就有了“pro”和“enterprise” 两个版本的产品。

包的动态装载和卸载

在大多数情况下,静态连接的dll或bpl已经可以满足要求了。但是如果我们不想发布bpl呢? “在指定目录中找不到动态链接库package1.bpl”,这是在应用程序终止前,我们所能得到 的唯一消息。或者,在模块化应用程序程序中,我们是否可以使用任意数量的插件?

我们需要在运行期动态连接到bpl。

对于dll 来说,有一个简单的方法,就是使用loadlibrary函数:

function loadlibrary(lplibfilename:pchar): hmodule;stdcall;

装载了dll之后,我们可以使用getprocaddress函数来调用dll的导出函 数和方法:

function getprocaddress(hmodule: hmodule;lpprocname:lpcstr): farproc; stdcall;

最后,我们使用freelibrary卸载dll:

function freelibrary(hlibmodule:hmodule): bool;stdcall;

下面这个例子中我们动态装载microsoft的htmlhelp库:

functiontform1.applicationevents1help(command: word; data: integer; varcallhelp: boolean):boolean;

type

tfnhtmlhelpa =function(hwndcaller: hwnd; pszfile: pansichar; ucommand: uint;dwdata:dword): hwnd; stdcall;

var

helpmodule:hmodule;

htmlhelp:tfnhtmlhelpa;

begin

result :=false;

helpmodule :=loadlibrary('hhctrl.ocx');

if helpmodule

begin

@htmlhelp :=getprocaddress(helpmodule, 'htmlhelpa');

if @htmlhelp

result :=htmlhelp(application.handle,pchar(application.helpfile), command,data)

freelibrary(helpmodule);

end;

callhelp :=false;

end;

动态装载bpl

我们可以用同样简单的方法来对付bpl,或者应该说基本上同 样简单。

我们可以使用loadpackage函数动态装载包:

function loadpackage(const name: string):hmodule;

然后使用getclass 函数创建一个tpersistentclass类型对象:

function getclass(const aclassname:string):tpersistentclass;

完成所有操作后,使用unloadpackage(module:hmodule);

让我们对原来的代码作一些小小的改动:

1、 在工程管理器中选中“project1.exe”;

2、 右击之并选择“options...”;

3、 选中“packages”选项页;

4 、 从“runtime packages”编辑框中移除“package1”,并点击ok按钮;

5、 在delphi的工具栏中,点击“remove file from project”按钮;

6、 选择“unit2 | form2”,并点击ok;

7、 现在在“unit1.pas”的源代码中,从uses子句中移除unit2;

8、 进入button1 的onclick时间代码中;

9、 添加两个hmodule和tpersistentclass类型的变量:

var

packagemodule:hmodule;

aclass:tpersistentclass;

10、使用loadpackage 函数装载pacakge1包:

packagemodule :=loadpackage('package1.bpl');

11、检查packagemodule是否为0;

12、使用getclass函数创建一个持久类型:

aclass := getclass('tform2');

13、如果这个持久类型不为nil,我们就可以向从前

一样创建并使用该类型的对象了:

withtcomponentclass(aclass).create(application) as tcustomform do

begin

showmodal;

free;

end;

14、最后,使用unloadpackage 过程卸载包:

unloadpackage(packagemodule);

15、保存工程。

下面是onclick事件处理器的完整清单:

procedure tform1.button1click(sender:tobject);

var

packagemodule:hmodule;

aclass:tpersistentclass;

begin

packagemodule:= loadpackage('package1.bpl');

ifpackagemodule

begin

aclass :=getclass('tform2');

if aclass

withtcomponentclass(aclass).create(application) as tcustomform do

begin

showmodal;

free;

end;

unloadpackage(packagemodule);

end;

end;

不幸的是,并不是这样就万事大吉了。

问题在于,getclass函数只能搜索到已经注册的类型。 通常在窗体中引用的窗体类和组件类会在窗体装载时自动注册。但是在我们的例子中,窗体无法提前装载。那么我们在哪里注册类型呢?答案是,在包中。包中的每 个单元都会在包装载的时候初始化,并在包卸载时清理。

现在回到我们的例子中:

1、 在工程管理器双击“package1.bpl”;

2、 点击“contains”部分“unit2”旁的+号;

3、 双击“unit2.pas”激活单元源代码编辑器;

4、 在文件的最后加入initialization部分;

5、 使用registerclass过程注册窗体的类型:

registerclass(tform2);

6、 添加一个finalization部分;

7、 使用unregisterclass过程反注册窗体的类 型:

unregisterclass(tform2);

8、 最后,保存并编译包。

现在我们可以安全的运行“project1”,它还会像从前 一样工作,但是现在你可以随心所欲的装载包了。

尾声

记住,无论你是静态还是动态的使用包,都要打开project |options | packages | build with runtime packages 选项。

在你卸载一个包之前,记得销毁所有该包中的类对象,并反注册所有已注册的类。下面的过程可能会对你有所帮助:

procedure dounloadpackage(module: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 (module = 0)or (hmodule(m.allocationbase) = module) then

application.components[i].free;

end;

unregistermoduleclasses(module);

unloadpackage(module);

end;

在装载包之前,应用程序需要知道所有已注册类的名字。改善这一情况的方法是建立一个注册机制,以便告诉应用程序所有 由包注册的类的名字。

实例

多重包:包不支持循环引用。也就是说,一个单元不能引用一个已经引用了该单元的单元(嘿嘿)。这使得调用窗体中的某 些值难以由被调用的方法设置。

解决这个问题的方法是,创建一些额外的包,这些包同时由调用对象和包中的对象引用。设想一下我们如何使application成为所有窗体的拥有者?变量application创 建于forms.pas 中,并包含在vcl50.bpl包 中。你大概注意到了你的应用程序既要将vcl50.pas编译进来,也同时你的包也需要(require) vcl50。

在我们第三个例子中,我们设计一个应用程序来显示客户信息,并且可根据需要(动态)显示客户订单。

那么我们可以从哪里开始呢?像所有的数据库应用

程序一样,我们需要连接。我们创建一个主数据模块,包含一个tdatabase连 接。然后我们将这个数据模块封装在一个包中(cst_main)。

现在在应用程序中,我们创建一个客户窗体,并引用datamodulemain(我 们静态的链接vcl50 和cst_main)。

然后我们创建一个新的包(cst_ordr),包中包含客户 订单窗体,并require cst_main。现在我们可以在应用程序中动态的装载cst_ordr了。既然在动态包装载以前主数据模块已经存在,cst_ordr就 可以直接使用应用程序的主数据模块实例了。

上图是此应用程序的功能示意图:

可换包:包的另一个应用实例是创建可更换包。实现这个功能并不需要包的动态装载能力。假设我们要发布一个有时间限制 的试用版的程序,如何实现这一点呢?

首先我们创建一个“splash”窗体,通常情况下是一幅带 有“试用”字样的图片,并在应用程序启动的过程中显示它。然后我们创建一个“about”窗体,提 供一些关于应用程序的信息。最后,我们创建一个用于测试软件是否过期的函数。我们把这两个窗体和这个函数封装到一个包中,并将它随试用版软件发布。

对于付费版软件,我们也创建一个“splash”窗体和一个 “about”窗体——要和前面的两个窗体类名相同——以及一个测试函数(什么也不做),并将它们 封装到同名的包中。

什么什么?你问这有用么?好吧,我们可以公开的发布一个试用版软件。如果某个客户购买了该应用程序,我们只需要发送 非试用版的包。这就大大简化了软件的发布过程,因为只需要一次安装和一次注册包升级。

包为delphi和c++builder开发社群打开了另一扇通往模块化设计的大门。通过包你不再需要到处传递窗体句柄,不再需要回调函数,不再需要其它dll技术。由此也缩短了模块化程序设计的开发周期。我们所要做的仅仅是让delphi的包为我们工作。


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值