应用程序编写完后,可以用两种方法展开(deploy)它(“展开”( Deploying)的含义是指把应用程序分发给用户)。可将应用程序分发给公众,或者分发给某个公司内的用户。不论用何种方法,都需要知道哪些选项是可用的。从根本上讲,有两种选择:静态链接或动态链接使用程序包,下面将讨论这些选项,以便编程人员作出适当的选择来展开其应用程序。下面先讲讲选项。
什么是程序包(What's a Package?)
在讨论选项之前,先给出程序包的定义。
一个程序包(package)就是一段编译过的代码,驻留在BPL扩展名的文件中。
这个解释可能还是让人不好明白,下面来进一步讲解。移去外表包装,程序包实质上就是带bpl扩展名的DLL(这样讲可能有点过,但在这里还是很贴切的)。Delphi中有两种类型的程序包:运行阶段(runtime)程序包和设计阶段(design)程序包。下面分别讲述这两种类型的程序包,以便大家理解程序包是如何工作的。
1、 运行阶段程序包(Runtime Packages)
运行阶段程序包包含应用程序需要运行的代码。虽然Delphi提供了众多不同的程序包,但最主要的程序包是VCL70.BPL,这个程序包包含了全部的基础VCL代码。如果要在应用程序中使用程序包,就要装入VCL70.BPL程序包并根据需要从中调用例程。如果应用程序是数据库应用程序,同样也要装入VCLDB70.BPL,并按需要从中调用例程。除这里提到的两个程序包外,还有其他的Delphi程序包。
除VCL程序包外,应用程序可能要用到其他程序包,当使用第三方组件或任何自己编写的组件时,就会出现这种情况。必须查看第三方组件的文档,搞清楚应用程序需要运行哪些程序包。下面先介绍设计阶段程序包,然后回过头来展开使用程序包的应用程序。
2、 设计阶段程序包(Design Packages)
大多数的Delphi组件都包含一个运行阶段程序包和一个设计阶段程序包。运行阶段程序包包含组件要运行的全部代码;设计阶段程序包包含组件在设计时要在窗体上运行的代码,包括属性编辑器和组件编辑器。
设计阶段程序包有一个Requires列表,用以通知Delphi要运行哪些程序包。设计阶段程序包总是要用运行时程序包中的代码,还可能需要一个或多个VCL程序包中的代码。一个程序包可以包含多个组件的代码,运行阶段程序包与设计阶段程序包都是如此,没有必要一个组件对应一个单独的程序包。
由于设计阶段程序包只包含在设计时显示组件所需的代码,所以它通常比相应的运行阶段程序包要小很多。Delphi仅仅在设计时使用设计阶段程序包,应用程序不使用设计阶段程序包。
静态链接与动态链接(Static Linking Versus Dynamic Linking)
上面,已经对程序包有了一些基本的了解,下面开始学静态链接和动态链接。
1、 静态链接(Static Linking)
当应用程序使用VCL静态链接时,它不再需要使用程序包;应用程序要运行的全部代码都直接链接到应用程序的可执行文件中,它是一个独立的程序,不需要任何的支持文件(程序包或DLLs)。
Note
任何规则都有例外,静态链接应用程序不需要任何DLLs支持的二个前提条件:
- 假定应用程序不是数据库应用程序。Delphi数据库应用程序运行时需要Borland Database Engine(BDE),BDE主要是由DLLs组成的集合,因此该应用程序是静态链接,也是需要使用DLLs的。
- 假定应用程序不使用任何的ActiveX控件。实际上ActiveX控件是DLL的一种形式,因此,当应用程序使用ActiveX控件时,它就不再是一个独立的应用程序。
Delphi提供了链接选项,可对其进行选择。静态链接是缺省选择,与动态链接相比,静态链接有两条主要优点:
- 编程人员不需要操心附件文件的安装,应用程序包含了全部要运行的代码,不需要运行时程序库的支持。
- 静态链接的应用程序一般总比需要程序包的应用程序要小。在讲动态链接的优点和缺点时还要谈到这一点。
静态链接有一个重要缺陷,但它只在使用到很多用户定义的DLLs的应用给程序中显露出来。这一缺陷是:在每个模块(主应用程序本身)和每个DLL中,VCL和RTL代码是重复的,这意味着代码中有不必要的重复。
例如,假设每个模块至少要200KB的VCL基本代码和RTL代码,并假定一个主应用程序要求10个支持它的DLLs(动态链接库)。这意味着当实际只用200KB的代码时,却要使用2200KB的代码(11个模块x 200KB)。应用程序和DLLs都是静态链接,在它们之间不能共享VCL和RTL代码。
2、 动态链接(Dynamic Linking)
动态链接,是指应用程序在运行阶段动态地装入它要用的程序库代码。对于Delphi应用程序,这意味着任何需要的程序包都是在运行阶段装入。需要的程序包中肯定会包括一个或多个VCL程序包,并且可能还要用第三方程序包。
Note
应用程序装入程序包是自动进行的,不必编写代码来装入程序包,Delphi负责程序包装入的工作。在静态链接的基础上选择动态链接不需要对代码做任何修改,只需要改变一下分发应用程序的方式。这一点在后面很快就会讲到。
动态链接相对于静态链接有一个主要优点:多个模块可共享代码。还记得前面举的“一个应用程序与10个支持它的DLLs”的例子么?使用动态链接,应用程序和它所有的DLLs可共享来自VCL程序包中的全部代码。每个模块至少可以减少200KB,因为所有的基本代码都包含在运行阶段DLLs中。当大型软件产品包含多个应用程序或许多DLLs时,这一优点就更加明显。
动态链接也存在两个问题。第一个问题是需要与应用程序一起传送的程序包和DLLs可能非常大,光一个主要的VCL程序包VCL70.BPL就要1.3MB。除基本VCL程序包外,应用程序可能还需要用到其他的程序包,这意味应用程序至少需要1.3MB的DLLs才可以运行。第二个问题是动态链接更加难以捉摸、更麻烦,这个问题可以归结成“版本问题”。为了讲清楚这个问题可以打个比方。假定有两个版本的Delphi,用Delphi7.02创建了一个应用程序,并选择动态链接,这就要求传送VCL程序包和RTL DLL,客户在他的机器上安装上这个应用程序后一切工作正常。与此同时,可用Delphi4.0创建一个应用程序,也采用动态链接。客户购买了该应用程序并安装它。这个安装程序是家庭制作的,不那么正规,它会覆盖原有的应用程序安装的程序包和DLLs。由于用Delphi4.0创建的程序包版本比另一种的低,两者不兼容,应用程序会突然退出运行。是否看出问题所在?
现实中,诸如Inprise的商用软件公司是这样解决这一个问题的:对一个软件的不同版本,用不同的文件名来命名其程序包和DLLs,并将版本信息嵌入到程序包和DLLs中(一个好的安装程序会自动检查版本号,并且只安装版本比系统中已存在的程序包版本高的程序包)。Borland公司的程序包不会出问题。
如果使用的组件出自一家不负责任的公司,那就很可能出问题。随着Internet的迅速发展,组件的来源范围非常广,更要重视这个问题。在很多情况下,无法预料会出什么乱子,所以在购买便宜组件或使用免费组件时要特别小心。
3、 到底哪个好?(So Which Is Better?)
大家可能会问:应该用静态链接还是动态链接?这个问题的答案取决于所编写的应用程序的类型。一般来说,如果编写小规模或中等规模的应用程序,应该用静态链接;如果编写大规模的应用给程序或用到很多DLLs的应用给程序,则应该用动态链接。
考察一个简单例子可能会使这个问题更直观些。前面我们创建了程序ScratchPad,使用静态链接,该程序编译后是427KB左右;如果使用动态链接,则EXE文件大小可降至22KB左右,但必须传送1.3MB的程序包。在这种情况下,动态链接不是一个好的选择。
在应用程序中使用运行阶段程序包(Using Runtime Packages in Your Applications)
如果选择使用动态链接,则只需修改工程选项中的一个设置。请按以下步骤操作:
(1)从主菜单上选【Project | Options】菜单项,弹出“Project Options”对话框;
(2)点击“Project Options”对话框中的Packages页面,并选中位于对于对话框底部的“Build with runtime package”选项;
(3)点击OK关闭“Project Options”对话框;
(4)重建(Rebuilt)该程序;
这就是全部要做的事情。切记:使用动态链接,不需要对代码作任何修改。
分发使用程序包的应用程序(Deploying Applications Using Packages)
要分发采用动态链接的应用程序,必须知道应用程序使用了哪些程序包。如果按照上一步的步骤,则可以肯定知道需要VCL70.BPL,可能还需要其他的VCL程序包,这取决于应用程序中使用的组件。
要照抄应用程序中用到的程序包,必须运行诸如TDUMP.EXE的工具并检查EXE引用的入口;TDUMP在Delphi安装目录的Bin目录下,要运行TDUMP,只需要打开命令提示符并转到应用程序所在目录,然后在命令行输入以下命令:
tdump scratchPad.exe
TDUMP立即以滚屏方式显示出信息,可按Pause键暂停以便查看显示信息。当然滚屏可能太快,可将TDUMP的输出定向到一个文本文件,这样查看文本文件即可。例如:
tdump scratchPad.exe > dump.txt
然后可在Code Editor中打开dump.txt文件查看其中的内容。
在TDUMP生成的文件中能看到类似下面的内容:
Section: Import ImportLookUpTblRVA:00000000 Time Stamp: 00000000 Forwarder Chain: 00000000 (index of first forwarder reference) Imports from rtl70.bpl __fastcall System::initialization() __fastcall System::Finalization() __fastcall System::RegisterModule(System::TLibModule *) System::__linkproc__ __fastcall LStrAsg(void *, const void *) System::__linkproc__ __fastcall LStrArrayClr(void *, int) System::__linkproc__ __fastcall LStrClr(void *) System::__linkproc__ __fastcall Halt0() System::__linkproc__ __fastcall StartExe(System::PackageInfoTable *, System::TLibModule *) System::__linkproc__ __fastcall HandleFinally() __fastcall System::TObject::Dispatch(void *) __fastcall System::TObject::FreeInstance() __fastcall System::TObject::NewInstance(System::TMetaClass *) Imports from kernel32.dll GetModuleHandleA Imports from vcl70.bpl __fastcall Forms::initialization() __fastcall Forms::Finalization() __fastcall Forms::TApplication::MessageBox(const char *, const char *, int) __fastcall Forms::TApplication::Run() __fastcall Forms::TApplication::CreateForm(System::TMetaClass *, void *) __fastcall Forms::TApplication::Initialize() __fastcall Forms::TApplication::SetTitle(const System::AnsiString) __stdcall Forms::TCustomForm::QueryInterface(const _GUID&, void *) __fastcall Forms::TCustomForm::UpdateActions() __fastcall Forms::TCustomForm::ShowModal() __fastcall Forms::TCustomForm::SetFocus() __fastcall Forms::TCustomForm::CloseQuery()
从这里面找出所有带.bpl扩展名的文件并记录下它们的文件名。记录下的文件名就是那些必须与应用程序一起分发的程序包。
Note
拥有一个好的安装程序可以节省很多时间并省去许多麻烦,Delphi7专业版和企业版中带有InstallShield Express打包程序,Wise Install打包程序也不错。好的安装程序能指示出应用程序所需的程序包,并自动将它们包括进去。不建议在任何环境下都自己编写安装程序,因为编写安装程序要考虑的问题太多,很容易因考虑不周而出问题。
大多数时候不需要在应用程序中使用运行阶段程序包,但有时候却又非使用程序包不可。