Delphi中的包介绍

原文地址:

http://www.bianceng.cn/Programming/Delphi/201011/20652.htm
http://www.bianceng.cn/Programming/Delphi/201011/20653_2.htm#
http://www.bianceng.cn/Programming/Delphi/201011/20654.htm

Delphi中的包(一):关于exe的编译、连接和执行


首先把需要分析的问题列出来:

什么是包?什么是exe?它们在组成上有什么不同?包跟dcu是什么关系?dcp 是干什么的?这些文件在编译时是什么关系?又是怎么装载的?装载了以后怎么 样操作包?dll可以exports,但是为什么delphi帮助中不提包的exports,但是 有些代码却又在包中使用exprots?

首先来看看delphi的编译过程。delphi的工程中有两类:包和程序,前者的 后缀为dpk,后者为dpr。从简单的开始,先来搞dpr。根据delphi的帮助文档, 一个典型的dpr文件的结构如下:

1 program Editor;
2
3   uses
4    Forms, {change to QForms in Linux}
5    REAbout in 'REAbout.pas' {AboutBox},
6    REMain in 'REMain.pas' {MainForm};
7
8   {$R *.res}
9
10   begin
11    Application.Title := 'Text Editor';
12    Application.CreateForm(TMainForm, MainForm);
13    Application.Run;
14   end.

其中10行到14行,begin…end很自然就是程序的执行入口。uses部分指明了 程序需要使用的一些Unit,这个就比较含糊了,为什么有的会用in指明源代码的 位置(这部分是自己向工程中添加的),有的如Forms这个部分,却又不需要? 那每个Unit又会uses其它Unit,这个问题似乎越来越复杂了。先看整个源代码的 结构:

编译器第一步首先遍历这张有向图,对每个Unit,如果有必要就对其进行编 译,生成对应的dcu。而这个“必要”问题,我开始以为是use这个Unit的语句是 带有in的,后来试验发现不对。因为在上面的情况下,Unit3并没有在Unit1的 Uses子句中指明路径,但是仍然正确产生了对应的dcu文件。后来使用filemon来 监视文件打开情况,发现过程是这样的:对于图中的每个节点,编译器按照当前 目录—project属性中的search path—IDE环境中的library path,这样的顺序 ,搜索节点对应的pas文件,没找到就再来一遍,但是这次搜索的是节点对应的 dcu文件。

现在编译搞定了,每个Unit(即pas文件)已经生成了对于的dcu文件,下面 的问题是连接。说到连接,问题就复杂了,连接有两种:静态和动态。静态连接 就是说把这些dcu全部合并到一起。这样,一个Unit对另一个Unit的调用,就成 了程序内部的事情了。这样的好处是快,而且简单,并发共享之类的问题都容易 处理。缺点是目标程序很大,而且如果现在要编写另一个程序,而Unit3可以重 用的话,则在连接时Unit3.dcu被再次拷贝。这样在两个程序同时运行时,内存 中会有两个Unit3的副本,比较浪费。动态连接就是说,两个程序在连接时,仅 仅只保留对Unit3的引用,而并不拷贝Unit3的内容。到运行时,把Unit3装入内 存,让两个程序公用。Dll和BPL都是动态连接的解决方案。问题在于,delphi中 关于连接的选项就只有project|Options|packages菜单中出现,“Build with runtime packages”这句话实在是太模糊了。所以还要再研究一下。

在程序执行的时候,我们可以通过view|debug window|moudles来查看有哪些 东西被加载到内存中去了,它们又包含哪些内容。简便起见,我们建立如下结构 的一个程序:

program ProjectEXE;
uses
Forms,
Windows,
UnitFormMain in 'UnitFormMain.pas' {FormMain};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.
unit UnitFormMain;
interface
uses
Windows, StdCtrls, Forms, UnitFormAnother,Classes, Controls;
type
TFormMain = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.Button1Click(Sender: TObject);
var
LForm:TFormAnother;
begin
LForm:=TFormAnother.Create(Application);
LForm.ShowModal;
LForm.Free;
end;
end.
unit UnitFormAnother;
interface
uses
Forms;
type
TFormAnother = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.

“Build with runtime packages”不打钩的时候,是静态连接的。有向图中 出现的所有Unit都包含在目标文件中了,整个exe有356k,而两个Unit各自只有 4k。

现在来动态。“Build with runtime packages”打钩,现在发现运行时 ProjectEXE.exe文件只包含四个部分:两个Form、一个SysInit.pas、一个 ProjectEXE.dpr;与此同时进程树里面多了两个bpl:rtl60和vcl60,它们的内 容就是刚才静态连接中出现的那些Unit。现在ProjectEXE.exe只有16k。也就是 说,有向图中的Unit,一部分放在exe中了,另一部分放在bpl中了。但是根据什 么来划分呢?是根据uses子句,还是根据这里“Build with runtime packages ”中的列表?继续测试,发现:如果列表中仅包含vcl60,则加载到内存中的还 是两个bpl加一个exe;如果列表中只包含rtl60,则内存中仅包含rtl60和exe, 但是exe的内容发生了变化:里面的Unit增多了,而且基本都是vcl60包里面的。我猜想应该是rtl和vcl包之间存在require关系。这个留到下一步再测试。但是 初步估计连接过程中,肯定会利用包列表,将那些已经在包中存在的Unit从exe 中排除出去。

在动态连接之后,还存在一个问题:装入。装入有两种策略,静态也称为自 动,由delphi生成代码,在装载exe之前,自动装入包;另一种是动态,即在程 序运行时通过编码,指定一个包,把它装入内存。问题在于,我必须搞清楚 delphi在什么情况下会自动装入一个包,什么情况下可以避免delphi自作聪明, 这样才能灵活地使用包。前面的试验中,只可以看出,在dpr文件执行到begin之 前,静态连接的的包就已经装入内存了。具体过程我也不清楚,等下一章开始写 自己的包,再来做实验吧。

Delphi中的包(二):关于bpl

写自己的dpk工程,以更改地检测我们的猜想。我们首先建立一个project group,包含三个工程:

program ProjectEXE;
uses
Forms,
Windows,
UnitFormMain in 'UnitFormMain.pas' {FormMain};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.
unit UnitFormMain;
interface
uses
Windows, StdCtrls, Forms, Classes, Controls;
type
TFormMain = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.Button1Click(Sender: TObject);
var
LForm:TForm2;
begin
  LForm:=TForm2.Create(Application);
LForm.ShowModal;
LForm.Free;
end;
end.
package Package1;
requires
vcl,
rtl;
contains
UnitFormAnother in 'UnitFormAnother.pas' {FormAnother},
UnitForm1 in 'UnitForm1.pas' {Form1};
end.
unit UnitFormAnother;
interface
uses
Forms;
type
TFormAnother = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
unit UnitForm1;
interface
uses
UnitFormAnother;
type
TForm1 = class(TFormAnother)
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.
package Package2;
requires
rtl,
vcl;
contains
UnitForm2 in 'UnitForm2.pas' {Form2};
end.
unit UnitForm2;
interface
uses
UnitFormAnother;
type
TForm2 = class(TFormAnother)
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
end.


小技巧:delphi对project group的编译是按照列表顺序从上到下进行的,因 此在有些时候,被require或者use的文件如果在下面,那么可能会提示找不到文 件。因此最好用文本编辑器调整一下bpg文件中的列表顺序。

现在我们看到,package工程的入口dpr文件结构中没有uses子句,但是取而 代之的是contains子句。从字面上说,这好像是指明这个包将会由哪些Unit组成 。这些Unit再去use别的unit,这样就又形成了一张有向图。是这样的吗?我们 将Package1的contain部分改成

contains

UnitForm1 in 'UnitForm1.pas' {Form1};

这样仅包含UnitForm1。而UnitForm1中因为存在继承关系,必然要use UnitFormAnother。于是自然package1中必须包含三个Unit:Package1.dpk、 UnitFormAnother.pas、UnitForm1.pas。然后编译,结果报警说:隐式地引入了 UnitFormAnother。先不管这个警告,然后改exe工程:把FormMain的 button1Click改成

procedure TFormMain.Button1Click(Sender: TObject);
var
LForm:TForm1;
begin
LForm:=TForm1.Create(Application);
LForm.ShowModal;
LForm.Free;
end;

当然,还要在FormMain的Uses里面加上UnitForm1,然后修改runtime package列表,加上package1。然后编译,调试,检查Module情况。结果发现 ProjectExe里面包含FormMain,而Package1.pbl里面包含我们所推测的三个Unit 。另一个试验是,如果在编译exe的时候,去掉列表中的package1,并且恰好能 让编译器找到FormAnother和Form1(源文件也好,dcu也好),也可以成功编译 。但是此时三个Form都跑到exe中间去了。

类似地,验证package2,发现:package2也可以编译过,前提是它能够找到 FormAnother,此时package2中包含两个Form;如果把package2的require部分改 成只依赖package1,那么最终编译出来的package2中则只含Form2。如果两个 package都包含FormAnother,而exe同时使用两个package的话,那么会产生编译 错误。(是啊,两个package同时加载一个类,当我要使用的时候,到底是谁提 供服务呢?)这种情况很容易发生,因为一方面FormAnother是Form1和Form2的 公共基类;另一方面,在Contain子句里面很容易不小心漏掉FormAnother。所以 编译器的提示还是很不错的,写程序还是按规矩办事,把contain写完整比较好 。

因此,现在基本可以得到结论:

由dpr文件的runtime library(或者dpk文件的requires子句)出发,再继续 搜索这些bpl require的bpl,直到获得所需的所有bpl文件。设这些bpl中包含的 Unit组成集合A。然后从dpr文件的uses子句(或dpk文件的contains子句)出发 ,生成工程所需Unit的集合B。则最后编译目标仅包含B-A。

集合B中的Unit,如果存在于集合A中,则在连接时不需要对应的dcu文件,有 bpl和bpc就好了。而B-A部分,要么必须有pas文件,要么需要有dcu文件。

现在,编译和连接的问题基本解决了,现在来研究加载。加载有两种,一种 是自动的,由delphi控制;一种是手动的,在程序中写LoadPackage。先来搞清 楚什么情况下会自动加载library。

测试是这样的,ProjectExe use UnitForm1, Package2 contains UnitForm2 requires package1,package1 contains UnitForm1和 UnitFormAnother。在ProjectExe的Package list里面仅有package2。运行结果 是:加载的包有rtl、vcl和package1,package2并没有出现。也就是说,自动装 入内存的包是那些存在于A集合中,且跟B集合有交集的包。所有想要完全手工加 载包,还必须要注意一些问题,起码它不能直接和间接地被require,包中的 Unit也不能在Use里面出现。换句话说,调用者完全不知道被调用包的情况下才 能避免自动装载。

既然调用者完全不知道被调用的包的信息,凭什么去调用呢?Delphi里面似 乎没有头文件之类的东西。怎么获取这个包的接口呢?


Delphi中的包(三):bpl和dl

曾经听说过“bpl就是一种特殊的dll”,但是没有想到这句话具 有这么大的意义。最近看到有人在属于某个dpk的Unit里面写export语句,觉得 非常惊奇。但是遍查delphi的help,与export相关的都是library。今天看了 《delphi源代码分析》,才知道上面这句话的含义。因此有下面的推论:

对dll工程来说,exports既可以写在工程文件里面,也可以写在Unit里面, 没什么区别。

bpl是一种dll。

上面的exports很正常。

其实按《delphi源代码分析》里面的说法,bpl只是在dll的基础上添加了一 些特性,比如检查重复Unit之类。操作系统认为bpl和dll是同一类型的文件。而 且书里面给出了对一个最简单bpl的TDump结果:

package NullPackage;
requires
rtl;
end.
E:\ls>tdump -m -ea:v NullPackage.bpl
Exports from NullPackage.bpl
7 exported name(s), 7 export addresse(s). Ordinal base is 1.
Sorted by RVA:
RVA   Ord. Hint Name
-------- ---- ---- ----
0000124C  1 0006 @GetPackageInfoTable
0000124C  2 0003 @NullPackage@@GetPackageInfoTable$qqrv
00001254  3 0002 @NullPackage@@PackageLoad$qqrv
00001254  7 0005 Initialize
00001260  4 0001 @NullPackage@@PackageUnload$qqrv
00001260  6 0004 Finalize
0000126C  5 0000 @NullPackage@initialization$qqrv

看到有好几个函数指向同一个地址了吗?这个大约就是exports的意思了。


delphi2-delphi2010 全支持 dcu 装换 pas -------------------------------------------------------------------------------- Project Dcu2Pas Version 1.3 Purpose Decompile a D2-D7, K1-K3's dcu(dpu) file to Delphi source as origin as possible, without or with minimal modifications, then recompile to new one under other Delphi versions Author Nengwen Zhuo(卓能文) Homes http://soarowl.uhome.net, http://www.websamba.com/soarowl, http://soarowl.0catch.com Emails soarowl@yeah.net, soarowl@sina.com.cn Released 2003-02-10 Lastest home/dl/Dcu2Pas.rar(zip) FOR RESTORE YOUR LOST SOURCES AND OPTIMIZE YOUR CODES ONLY!!! -------------------------------------------------------------------------------- This project is in very early stage, no document yet. To see some features, reference my test cases file -- test.pas and the output file test.int, please. The output maybe more clear organised than source files!!! For further development, any suggestions and ideas are welcome. I have no time to update my homepages for a long time ;-) -------------------------------------------------------------------------------- Usage Run Dcu2pas, set properly options, select a desired dcu file double click to decompile it -------------------------------------------------------------------------------- Features - Supports the following type declarations and their typed consts: * Type redeclarations, for example: type MyTypeString = type String; * All integer types(Byte, Cardinal, Int64, Integer, Longint, Longword, Shortint, Smallint, Word) * All char types(AnsiChar, Char, WideChar) * All boolean types(Boolean, ByteBool, LongBool, WordBool) * Enumerated type, subrange of enumerated type and set of enumerated type For examples: type Size = (Small = 5, Medium = 10, Large = Small + Medium); TMySize = Medium..Large; type TEnumSet = set of (Club, Diamond, Heart, Spade); const vcSet4: TEnumSet = [Club,Spade]; * All real types(Comp, Currency, Double, Extended, Real, Real48, Single) * All string types(AnsiString, ShortString, String, String[XX], WideString) * Array type(with/without packed keyword), dynamic array and multidimensional dynamic array * Set type * Record type, with any variant parts in records. But if a record has any variant parts in it, the typed const may can't correctly decompile * Function/procedure type declaration - Support threadvar - Support resourcestring - Support class type, interface inheritation - Support object type - Support interface - Code decompiler to BASM, then a decpompiled file can recompile under other Delphi versions... - Code decompiler to Object-Pascal. I have some ideas, but I have no time, maybe in near future, I will code for it! -------------------------------------------------------------------------------- Update history Legend: - Bug fixed * Algorithm enhanced + New featuer added Ver2.0 Ver1.3 2003-02-10 * Code decompiler redesigned * Partly support D7 Ver1.2 2002-07-28 + Delete procedure, contructor, destructor frame 2002-07-18 + Procedure variable const + Now can distinguish: vc1: PChar ='test'; vc2: PChar = @vc1; 2002-05-28(Ver1.1) + Now correctly process interface properties and argument default values + Support resources, for example: {%R *.dfm}, {$R *.res} 2002-05-26 + Support default arguments + Support interface 2002-05-25 + Support object type + Support resourcestring + Support threadvar + Support function/procedure type 2002-05-24 + Support file type + Support set of enumerated type const decompiler + Support enumerated type const decompiler + Support subrange of enumerated type + Support dynamic array type - Array [enumerted type] of ... - Bugs in [packed] array fixed - Bugs in [packed] record fixed - Bugs in enumerated type fixed - Bugs in classify interface and implement declarations fixed
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值