2012年2月3日

Dephil之使用程序包(Using Packages)

应用程序编写完后,可以用两种方法展开(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支持的二个前提条件:

  1. 假定应用程序不是数据库应用程序。Delphi数据库应用程序运行时需要Borland Database Engine(BDE),BDE主要是由DLLs组成的集合,因此该应用程序是静态链接,也是需要使用DLLs的。
  2. 假定应用程序不使用任何的ActiveX控件。实际上ActiveX控件是DLL的一种形式,因此,当应用程序使用ActiveX控件时,它就不再是一个独立的应用程序。

Delphi提供了链接选项,可对其进行选择。静态链接是缺省选择,与动态链接相比,静态链接有两条主要优点:

  1. 编程人员不需要操心附件文件的安装,应用程序包含了全部要运行的代码,不需要运行时程序库的支持。
  2. 静态链接的应用程序一般总比需要程序包的应用程序要小。在讲动态链接的优点和缺点时还要谈到这一点。

静态链接有一个重要缺陷,但它只在使用到很多用户定义的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”选项;

0254

(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打包程序也不错。好的安装程序能指示出应用程序所需的程序包,并自动将它们包括进去。不建议在任何环境下都自己编写安装程序,因为编写安装程序要考虑的问题太多,很容易因考虑不周而出问题。

大多数时候不需要在应用程序中使用运行阶段程序包,但有时候却又非使用程序包不可。

posted @ 2012-02-03 11:09 瓢虫Monster 阅读(830) 评论(3) 编辑

Delphi之使用资源文件(Using Resource Files)

New Term

每个Windows应用程序都使用资源。资源(Resources)不是可执行代码,但它属于程序的一部分元素。

典型的Windows程序的资源有:

  • 加速器(Accelerators)
  • 位图(Bitmaps)
  • 光标(Cursors)
  • 对话框(Dialog boxes)
  • 图标(Icons)
  • 菜单(Menus)
  • 数据表(Data tables)
  • 字串表(String tables)
  • 版本信息(Version information)
  • 用户定义的专用资源(User-defined specialty resources)如声音和视频文件

Note

用Project Options对话框的Version Info页面可轻松地将版本信息加到Delphi工程中。如下图:

0246

资源一般包括在扩展名为.rc的资源脚本文件中(resource script file),资源文件就是文本文件。资源文件用资源编译器编译,并在链接时加到应用程序的.exe文件中。

通常大家认为资源要加到可执行文件中,但是有些资源,如位图、字符串表、波形文件,既可以放到外部文件中(.bmp、.txt、.wav),也可加到.exe文件并包含到应用程序文件中。

把资源放到.exe文件中有两条主要优点:

  • 存取资源的速度更快。因为在一个可执行文件中查找资源花的事件比从磁盘文件中装入资源花的时间要少。
  • 程序和资源可一起包含到单个单元(即.exe文件)中,而不需要一大堆的支持文件。

它的不足之处是:会使.exe文件稍稍增大。其大小不会比外部资源文件加可执行文件大。但是增加大小会使加载该程序的时间加长。

是把资源存为外部资源文件,还是把资源放到.exe文件中,这得由编程人员自己定。但要记住的是:这两种方式用哪一种都行(甚至可以在同一个程序中使用两种方式)。

Delphi中的资源(Resources in Delphi)

传统的Windows程序几乎都至少包含一个对话框和一个图标。但是,Delphi应用程序有所不同。首先,Delphi应用程序中没有真正意义上的对话框,实质上也就是没有对话框资源(Delphi中存储的窗体是资源,但它们是RCDATA资源,而不是对话框资源)。

Delphi应用程序有传统意义的图标资源。创建应用程序时,Delphi负责创建图标资源文件。类似地,在为Speedbutton、Image组件或BitBtn组件选择位图时,Delphi将所选位图文件包含到窗体资源中(作为窗体资源的一部分)。在建立应用程序时,窗体和它的全部资源一起包括到程序文件中。这些都是自动处理的。

有时需要在通常的Delphi处理中以外使用资源。例如,要制作动画,必须有一系列的位图,将它们装载进来作为可以最快速度执行的资源。在这种情况下,就需要知道如何把资源捆绑到Delphi应用程序中。

把资源文件捆绑到可执行文件中是件非常容易的事,实际创建资源却要困难的多。如果有一个好的资源编辑器,创建诸如位图、图标和光标之类的基本资源并不困难,但创建具有专业化品质的3D位图和图标却是一项艺术性的工作。我们肯定遇到过很多不错的程序,但它们的位图按钮实在难看。我们可以利用Delphi自带的Image Editor创建位图、图标和光标。

如果要创建字符串资源、用户数据资源、波形文件资源或其他专用资源,则可能需要第三方资源编辑器。

Note

如果手头有老版本的Borland Pascal,可使用其中的Resource Workshop编辑器编辑专用资源。创建好资源后,会形成一个.rc文件,Delphi中带有Borland Resource Compiler,用Borland Resource Compiler(BRCC32.EXE)将它编译成.res文件。从技术上讲,可以用任何一种文本编辑器创建.rc文件并用资源编译器编译它,但使用资源编辑器创建资源要容易的多。

现在在推荐一个款比较不错的第三方资源编辑器Resource Builder,该软件界面简洁,使用简单,大家可以百度一下。

编译资源文件(Compiling Resource Files)

资源文件创建好后,要用资源编译器来编译它。编译资源文件的方法有两种:

  • 从命令行手工编译资源文件。
  • 添加一个批处理文件工程到工程组。

用其中任意一个方法,编译完后都得到一个.res文件,将它链接到应用程序中。

1、 从命令行编译(Compiling from the Command Line)

从命令行编译资源文件,只需打开Windows中的命令提示框,并输入一行与下面相似的命令:

brcc32 jjres.rc

0247

当然,必须保证当前系统目录为Delphi安装目录的Bin目录下,如果不是,则必须输入BRCC32.EXE的完整路径。这个资源编译器速度非常快,甚至不等察觉,它就把资源脚本文件编译完成了。

2、 使用批处理文件工程(Using a Batch File Project)

添加一个批处理文件到工程组,与从命令行编译一样简单,并且还有一个好处:保证资源文件总是最新的。要搞清楚批处理文件如何工作,可执行下面的步骤:

(1)创建一个新的应用程序;

(2)选择【View | Project Manager】打开“Project Manager”工程管理器,如下图:

0248

(3)点击“Project Manager”工具栏上的“Add New Project”按钮,显示Object Repository对象库,如下:

0249

(4)双击Batch File图标来创建文件工程。将该批处理文件工程以Project2的名称添加到“Project Manager”中,如下:

0250

(5)用鼠标右键点击批处理文件节点并选择Save,将文件保存为test.bat

0251

(6)用鼠标右键再次点击批处理文件节点并选择【Edit/Options】,会弹出“Batch File Options”对话框;

0252

(7)在Command文本框内输入下列正文:

del myfile.res

brcc32 myfile.rc

(8)点击OK关闭“Batch File Options”对话框。

这个练习锁做的就是创建一个批处理文件,当编译工程组时,它就会执行。第(7)步输入的批处理文件命令删除一个名为myfile.res文件,并调用Delphi资源编译器编译myfile.rc文件。用资源编译器编译myfile.rc文件会生成一个名为myfile.res的文件。

工程组中的下一个工程可能要使用myfile.res。呵呵,那为什么要先删除myfile.res文件呢?这是因为,删除该文件后,就能知道资源编译器是重新建立了这个文件。如果资源编译器创建资源失败,则任何要使用这个资源文件的工程都将编译失败,并报告编译错误,提示编程人员,建立资源文件出错。

把资源文件链接到你的可执行文件中(Linking Resource Files to Your Executable)

编译好资源文件后,要把编译后的资源文件链接到程序的可执行文件中,可使用$R编译器指令。例如,要链接包含在myfile.res文件中的二进制资源,可在主窗体单元的开头处插入下面一行:

{$R myfile.res}

就这么简单!只要指定的文件存在,Delphi就会在链接时把这个编译过的资源添加到可执行文件中。

使用资源的样本程序(A Sample Program Using Resources)

下面的清单中,列出了Jumping Jack的程序的主窗体单元。这个程序显示一个带声音效果的简单动画。主窗体上有两个按钮:一个Image组件和一个Label组件。Jumping Jack程序说说明了资源在Delphi应用程序中的使用。它特别说明了如何加载以资源文件形式存储的位图,如何加载并显示字符串资源,以及如何播放资源文件中的波形音频。清单后面还列出了部分资源文件。先看看清单,然后再分析程序。

JJMain.pas清单

unit JmpJackU;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, MMSystem;

{$R JJRES.RES}

const
  IDS_UP = 101;
  IDS_DOWN = 102;

type
  TMainForm = class(TForm)
    Image: TImage;
    Label1: TLabel;
    Start: TButton;
    Stop: TButton;
    procedure FormCreate(Sender: TObject);
    procedure StartClick(Sender: TObject);
    procedure StopClick(Sender: TObject);
  private
    { Private declarations }
    Done: Boolean;
    procedure DrawImage(var Name: string);
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.DrawImage(var Name: string);
begin
  { 从资源文件中读取位图,通过资源名称}
  Image.Picture.Bitmap.LoadFromResourceName(HInstance, Name);
  { 让Image有机会显示位图}
  Application.ProcessMessages;
  { 延缓,让动画变慢}
  Sleep(20);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Image.Picture.Bitmap.LoadFromResourceName(HInstance, 'ID_BITMAP1');
end;

procedure TMainForm.StartClick(Sender: TObject);
var
  s: string;
  ResName: string;
  i: Integer;
  Buff: array[0..9] of Char;
begin
  s := 'ID_BITMAP';
  Done := False;
  while not Done do
  begin
    for i := 1 to 5 do
    begin
      ResName := s + IntToStr(i);
      DrawImage(ResName);
    end;
    LoadString(HInstance, IDS_UP, Buff, SizeOf(Buff));
    Label1.Caption := Buff;
    Label1.Refresh;
    PlaySound('ID_WAVEUP', HInstance, SND_ASYNC or SND_RESOURCE);
    Sleep(200);
    for i := 5 downto 1 do
    begin
      ResName := s + IntToStr(i);
      DrawImage(ResName);
    end;
    PlaySound('ID_WAVEDOWN', HInstance, SND_ASYNC or SND_RESOURCE);
    LoadString(HInstance, IDS_DOWN, Buff, SizeOf(Buff));
    label1.Caption := Buff;
    Label1.Refresh;
    Sleep(200);
  end;

end;

procedure TMainForm.StopClick(Sender: TObject);
begin
  { 当“Stop”按钮按下时,告诉循环停止}
  Done := True;
end;

end.

JJRec.rc清单

STRINGTABLE
BEGIN
101, "Up"
102, "Down"
END

ID_WAVEUP WAVE "up.wav"
ID_WAVEDOWN WAVE "down.wav"

ID_BITMAP1 BITMAP
MOVEABLE PURE LOADONCALL DISCARDABLE
LANGUAGE LANG_NEUTRAL, 0
BEGIN
'42 4D 76 02 00 00 00 00 00 00 76 00 00 00 28 00 '
'00 00 20 00 00 00 20 00 00 00 01 00 04 00 00 00 '
'00 00 00 02 00 00 00 00 00 00 00 00 00 00 10 00 '
'00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80 '
'00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80 '
'00 00 C0 C0 C0 00 80 80 80 00 00 00 FF 00 00 FF '
'00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF '
'00 00 FF FF FF 00 BB BB BB BB BB BB BB BB BB BB '
'BB BB BB BB BB BB BB BB BB BB BB BB BB BB BB BB '
'BB BB BB BB BB BB BB BB BB BB BB BB BB B0 B0 BB '
'BB BB BB BB BB BB BB BB BB BB BB BB BB B0 B0 BB '

此处省略了部分内容,详细内容请查看示例代码

 

分析:

主窗体类声明中声明了一个Done的布尔型字段,Done用于确定何时终止动画。DrawImage方法用于在Image组件中显示位图。

注意:上列代码中,使用了两个Windows API函数来加载字符串和波形文件资源。在StartClick方法中,LoadString函数加载一个字符串到到一个字符数组缓冲区内。然后将该字符数组分配给窗体上的Label组件的Caption属性。

PlaySound函数用于播放波形文件资源。PlaySound函数用SND_ASYNC标志通知Windows开始播放音频并立即将控制返回给程序,这使得在播放音频的同时,动画能继续下去。SND_RESOURCE标志通知Windows,声音是一个资源,而不是磁盘上的一个文件。LoadString和PlaySound函数都使用HInstance全局变量通知Windows到可执行文件中去查找资源。装入位图资源,使用VCL方法LoadFromResourceName。

而资源文件JJRec.rc中的前5行说明字符串在资源脚本文件中的格式;使用文本编辑器创建字符串表非常容易。接下去是为两个波形文件创建各自的WAVE资源,这两个波形文件已存在该工程目录中。资源编译器一看到WAVE声明,它就会读声音文件并将它们编译进二进制资源文件。

Note

从上面的清单中看出,使用文本编辑器来创建某些类型的资源是比较容易的事。如果位图和波形音频文件存为了外部文件,可像上面的清单那样将它们包含到.RC文件中,并用资源编译器将它们编译为二进制资源文件。其中二进制资源文件可链接到应用程序的可执行文件中。

上面的资源清单只是部分代码,用传统的资源编辑器创建的位图常常以数字数据的形式包含到资源文件中,位图的资源描述可以很长。Jumping Jack位图资源描述大约有200行,因此未在清单中全部列出。下图给出了Jumping Jack启动后的界面。

0253

为应用程序创建附加资源不是什么高深的学问,但也不是很简单的事情。要搞清楚它们如何协调配合要花一定的事件。

以上代码均在Delphi7中测试通过,示例代码下载:JumpingJack.rar

posted @ 2012-02-03 02:12 瓢虫Monster 阅读(880) 评论(0) 编辑