Delphi中的动态包,有详细建立包的步骤(答案很简单:因为包的功能强大)
为什么要使用包?
答案很简单:因为包的功能强大。设计期包(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中的动态包,有详细建立包的步骤(答案很简单:因为包的功能强大)的更多相关文章
- [转]Delphi 中动态链接库(dll)的建立和使用
动态链接库是一个能够被应用程序和其它的DLL调用的过程和函数的集合体,它里面包含的是公共代码或资源.由于DLL代码使用了内存共享技术,在某些地方windows也给了DLL一些更高的权限,因而DLL中可 ...
- 在Delphi中如何动态创建dbf数据库(二)?
unit Form_ToChangCSVforDBFU; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics ...
- 在Delphi中如何动态创建dbf数据库(一)?
table2.Close; table2.Active:=false; table2.Exclusive:=true; table2.TableName:='h:\gzkd\sds'; table2. ...
- 12个Python游戏中的龙穴探险,快速掌握基础,其实很简单
越来越多的人学习python编程,但更多的人,拿着教程却不知道该怎么学. 今天我给大家举一个例子,是我自己学习python时,用到的方法. 首先,我是一名普通的程序员,相对于十几年开发经验的程 ...
- 深入理解javascript中的动态集合——NodeList、HTMLCollection和NamedNodeMap
× 目录 [1]NodeList [2]HTMLCollection [3]NamedNodeMap[4]注意事项 前面的话 一说起动态集合,多数人可能都有所了解.但是,如果再深入些,有哪些动态集合, ...
- Delphi中的异常处理(10种异常来源、处理、精确处理)
一.异常的来源 在Delphi应用程序中,下列的情况都比较有可能产生异常. 1.文件处理 2.内存分配 3.windows资源 4.运行时创建对象和窗体 5.硬件和操作系统冲突 6.网络问题 7.数据 ...
- 在Delphi中使用系统对应文件类型的图标
在应用程序的编写中,组合框(ComboBox).列表框(ListBox).等常见的部件,通常不仅要用于显示文字,而且还要显示其与文字相关的图标.在一般的Windows应用程序中,这些图标的显示都要随列 ...
- JVM中的动态语言支持简介
抽丝剥茧 细说架构那些事——[优锐课] 从版本6开始,JVM已扩展为支持现代动态语言(也称为脚本语言).Java8的发行为这一领域提供了更多动力.感到这种支持的必要性是因为Java作为一种语言固有地是 ...
- delphi中使用MSWINSCK.OCX控件
1.首先是把winsck控件导入到delphi中,就是导入一个ActiveX控件,步骤略过. 2.将导入的winsck控件拖入你的Form中. 3.对winsck进行基本设置(IP,Port). 4. ...
随机推荐
- 自学Zabbix5.1 zabbix maintenance维护周期
自学Zabbix5.1 zabbix maintenance维护周期 1. 概述 你可以定义维护周期在主机或主机组里.这里有2种维护状态: 依旧收集数据 继续对目标的监控数据的收集 暂停收集数据 ...
- Android 视频 教程 源码 电子书 网址
资源名称 资源地址 下载量 好评率8天快速掌握Android视频教程67集(附源码)http://down.51cto.com/zt/2197 32157Android开发入门之实战技巧和源码 htt ...
- SpringMvc的Url映射和传参案例
Springmvc的基本使用,包括url映射.参数映射.页面跳转.ajax和文件上传 以前学习的时候写的代码案例,今天整理笔记的时候找到了,很久没有来园子了,发上来当个在线笔记用吧,免的时间长了又忘了 ...
- 那些神奇的before和after使用方法
在英文单词里面:before是在什么之前.after是在什么之后.诚然,在我们的css里面, 通过使用before和after伪类元素,可以在我们想要的元素前面或者后面插入内容. 下面是使用befor ...
- 常量引用 const T&
1.引用本身不是对象,只是引用对象的别名,没有内存空间产生 2.引用必须严格类型匹配 3.而常量引用 const T& 可以引用字面值常量及表达式 其实也就是右值,且常量引用的不同与T类型对象 ...
- nginx 中配置多个location并解决js/css/jpg/等的加载问题
2017-11-09 22:07 277人阅读 评论(0) 收藏 举报 分类: linux(1) 版权声明:如有版权问题,请私信我. ECS:阿里云 系统:ubuntu 16.04 我的配置文件位 ...
- java包的所有类生成class
javac的编译单位其实就是单个的java文件,为了达到同时编译多个java文件的目的,可以将所需编译的java文件路径保存在一个txt中,比如sourcelist.txt,以换行为分隔符(这个过程称 ...
- 20190312 Windows安装Kafka
1. 下载 使用版本2.1.1 官网下载地址 2. 安装 2.1. 前提 已安装ZooKeeper,可参考 20190311 Windows安装ZooKeeper 进行安装ZooKeeper 2.2. ...
- n的阶乘-编程2.md
计算阶乘n!: 注意处理结果溢出 方法: 用数组来存储结果 /** * 计算阶乘n!: 注意处理结果溢出 * 方法: 用数组来存储结果 */ public class PowerN { // Time ...
- 直接用<img> 的src属性显示base64转码后的字符串成图片【原】
直接用<img> 的src属性显示base64转码后的字符串成图片 <img src="data:image/gif;base64,base64转码后的字符串" ...