为什么要使用包?

答案很简单:因为包的功能强大。设计期包(design-time package)简化了自定义组件的发布和安装;而运行期包(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库:

function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer; var CallHelp: 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 <> 0 then

begin

@HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA');

if @HtmlHelp <> nil then

Result := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;

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,我们就可以向从前

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

with TComponentClass(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');

if PackageModule <> 0 then

begin

AClass := GetClass('TForm2');

if AClass <> nil then

with TComponentClass(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的包为我们工作。

http://www.cnblogs.com/gxch/archive/2011/04/23/bpl.html

Delphi中的动态包,有详细建立包的步骤(答案很简单:因为包的功能强大)的更多相关文章

  1. [转]Delphi 中动态链接库(dll)的建立和使用

    动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源.由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可 ...

  2. 在Delphi中如何动态创建dbf数据库(二)?

    unit Form_ToChangCSVforDBFU; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics ...

  3. 在Delphi中如何动态创建dbf数据库(一)?

    table2.Close; table2.Active:=false; table2.Exclusive:=true; table2.TableName:='h:\gzkd\sds'; table2. ...

  4. 12个Python游戏中的龙穴探险,快速掌握基础,其实很简单

    越来越多的人学习python编程,但更多的人,拿着教程却不知道该怎么学. 今天我给大家举一个例子,是我自己学习python时,用到的方法.     首先,我是一名普通的程序员,相对于十几年开发经验的程 ...

  5. 深入理解javascript中的动态集合——NodeList、HTMLCollection和NamedNodeMap

    × 目录 [1]NodeList [2]HTMLCollection [3]NamedNodeMap[4]注意事项 前面的话 一说起动态集合,多数人可能都有所了解.但是,如果再深入些,有哪些动态集合, ...

  6. Delphi中的异常处理(10种异常来源、处理、精确处理)

    一.异常的来源 在Delphi应用程序中,下列的情况都比较有可能产生异常. 1.文件处理 2.内存分配 3.windows资源 4.运行时创建对象和窗体 5.硬件和操作系统冲突 6.网络问题 7.数据 ...

  7. 在Delphi中使用系统对应文件类型的图标

    在应用程序的编写中,组合框(ComboBox).列表框(ListBox).等常见的部件,通常不仅要用于显示文字,而且还要显示其与文字相关的图标.在一般的Windows应用程序中,这些图标的显示都要随列 ...

  8. JVM中的动态语言支持简介

    抽丝剥茧 细说架构那些事——[优锐课] 从版本6开始,JVM已扩展为支持现代动态语言(也称为脚本语言).Java8的发行为这一领域提供了更多动力.感到这种支持的必要性是因为Java作为一种语言固有地是 ...

  9. delphi中使用MSWINSCK.OCX控件

    1.首先是把winsck控件导入到delphi中,就是导入一个ActiveX控件,步骤略过. 2.将导入的winsck控件拖入你的Form中. 3.对winsck进行基本设置(IP,Port). 4. ...

随机推荐

  1. 洛谷P2668 斗地主

    好,终于搞完了这一道毒瘤题...... 先想到搜索,然后想到状压,发现数据组数很多,又是随机,还是决定用搜索. 先搜出的多的,于是顺序是三个顺子,然后按照多到少搜带牌,最后是不带牌. 大体思路很简单, ...

  2. 外显子分析报错解决方案bin field of BAM record does not equal value computed based on alignment start and end, and length of sequence to which read is aligned

    以下链接给出了解决方案:https://gatkforums.broadinstitute.org/gatk/discussion/4290/sam-bin-field-error-for-the-g ...

  3. 常量引用 const T&

    1.引用本身不是对象,只是引用对象的别名,没有内存空间产生 2.引用必须严格类型匹配 3.而常量引用 const T& 可以引用字面值常量及表达式 其实也就是右值,且常量引用的不同与T类型对象 ...

  4. poj 3061(二分 or 尺取法)

    传送门:Problem 3061 https://www.cnblogs.com/violet-acmer/p/9793209.html 马上就要去上课了,先献上二分AC代码,其余的有空再补 题意: ...

  5. hdu 2586(裸LCA)

    传送门 题意: 某村庄有n个小屋,n-1条道路连接着n个小屋(无环),求村庄A到村庄B的距离,要求是经过任一村庄不超过一次. 题解: 求出 lca = LCA(u,v) , 然后答案便是dist[u] ...

  6. IntelliJ IDEA和Eclipse最常用的快捷键对应表:

    描述 Eclipse Intellij 代码补全 Ctrl+space Ctrl+space 打开类或者接口 (两个IDE都支持使用“驼峰字符”前缀的方式来过滤查找列表,进而轻松完成搜索:比如:可 以 ...

  7. Python基础【day02】:数据运算(二)

    本节内容 数据运算 表达式while 循环 一.数据运算 算数运算: 比较运算: 赋值运算: 逻辑运算: 成员运算: 身份运算: 位运算: #!/usr/bin/python a = 60 # 60 ...

  8. H5静态资源本地化实践

    现在很多app都是通过webview内嵌H5的页面,这种方式的好处就是无需发版就能更新线上的内容,而且可以做到多平台的统一开发,节约开发成本.但是这种模式也带来了一定的问题,web开发很大程度依赖于网 ...

  9. centos7安装minikube

    安装之前需要在bios中开启虚拟化功能.这里是在vmvare虚拟机中安装.开启步骤如下: 下载virtualbox 官网地址:https://www.virtualbox.org/ [virtualb ...

  10. Java基础编程题——打印九九乘法表

    package com.yangzl.basic; /** * 九九乘法表 * @author Administrator * */ public class Nine_Nine_Multiplica ...