用Delphi制作BPL包

转自
http://www.360doc.com/content/13/0602/15/1426269_289921363.shtml
http://13820129409.blog.163.com/blog/static/408625922010910114416986/
http://dukedingding.blog.sohu.com/51851377.html

背景

GCM3构造时间长的问题由来已久。伴随着时间的流逝,系统功能越来越强大,模块越来越多,目前仅GCM一项,需要编译的客户端dll达389个之多。在编译机上,一次完整的编译过程(GCM+GCC_PE)耗时更是长达100分钟。经常出现这样的情况:开发人员和测试人员并没有别的事情,就是在等待编译版本,比如发版前的完整构造,或者每日构造失败需要重新编译,这就浪费了我们很多宝贵的工作时间。

解决方案

GCM3构造过程的环节很多,主要包括源码下载、编译、数据库模板和升级脚本的生成、文件压缩和打包发布等。其中编译是一个重头,占到一半以上的时间。其中每个工程编译时间大约为7、8秒,这样仅GCM的客户端dll编译就需要40到50分钟时间。于是如何减少编译时间成为减少总构造时间的一个解决思路。

由于GCM本身框架结构比较庞大,模块里的类继承层次也比较多,很多重复性的代码都被提取出来放在基类中,但在编译过程中这些基类中的代码将会被不断的重复编译。比如有200个工程中的主窗体继承自基类TListDetailForm,该类定义于单元文件ListDetailFrm.pas中,那么在编译过程中ListDetailFrm.pas将被重复编译200次。同样,由于GCM框架结构中继承关系为:TListDetailForm -> TDataBaseForm -> TBaseForm -> TBlankForm,因此这些基类所在的单元文件都会被重复编译200次。

为了避免这种无谓的重复编译,我们可以把公共代码(包括基类所在单元以及一些公共单元)编译成一个独立的部分,其它的工程都引用它。这样公共部分的代码就只需要编译一次,大大减少了编译的时间。

Delphi提供了带包编译(Build with runtime packages)的机制来避免重复编译相同的源码,方法如下:在编译工程时勾选带包编译方式(位于Project -> Options… -> Packages ->Runtime packages),并把前面编译好的BPL添加到运行时包中。当然,这里的BPL就是包含公共代码的包。

这种做法优点:

1. 提高FinalBuilder编译速度,缩短编译时间。同时也提高了打包的速度;

2. 减小编译出来的文件(如exe和dll文件)大小,同时减小了安装程序的大小,顺便提高了安装速度;

3. 若基类或公共单元做了修改,某些情况下,可以避免所有模块重编一遍,只需把这个包重新编一下即可。

实际做法

基本上,GCM的基类和公共单元都位于SHARE\Common以及3编码阶段\3.1版SOURCE\_Common两个目录下。我们创建了一个运行期包GCMCommonPackage.bpl,把这两个目录下的单元文件(*.pas)基本上都打入了包里。编译其它工程时,都引用这个运行期包。

效果

采用上述编译改造策略后,对GCM项目进行一次全编译(GCM+GCC_PE),编译时间由原来的1小时40分左右减少为1个小时左右,减小了约40%的时间。提取BPL以前,编译每个模块平均需要7——8秒钟。提取以后的时间减少到3——6秒。用ASPack压缩后,每个dll由原来的200——300K减小到了60——70K,平均大小减小了70%左右。另外安装时间也缩短了一些。

目前存在的问题

由于编译和构造本身的复杂性以及GCM各模块之间存在一些比较特殊的千丝万缕的联系,改为带包编译以后还存在。例如客户端dll必须加编译选项“dllmode”,而服务器端COM以及其它exe文件不能加“dllmode”编译选项,这样GCMCommonPackage.bpl就不能满足所有工程。另由于隐式包含,一些非公共的单元也被包含到包里了,可能会带来一定的隐患,参见稍后的详述。

附1 注意事项

1、 BPL包的优先级高于引用单元文件的优先级

例如:GCMCommonPackage.bpl引用了单元文件GCMUtils.pas,工程文件pCL_ZRZCD引用了GCMCommonPackage.bpl,则对GCMUtils.pas的引用,一定是bpl里的。即工程中某单元文件若uses了GCMUtils,是从BPL里面取的,即使把GCMUtils.pas添加到工程中,仍然是这样。因此,如果修改了GCMUtils.pas,你会发现根本没有效果,这是因为BPL中的代码没变。这就引出了下面的调试问题。

2、 调试

虽然GCMUtils是从BPL中取的,但是仍然能在GCMUtils中设置断点调试。这就有个问题:如果修改了GCMUtils的代码而BPL没有重编,那断点岂不是无效吗?我试了试,发现断点所停的位置,并不是显示的所在行,而似乎是编译BPL时的那一行。例如编译BPL时,第10行是ShowMessage,现在移到第20行了,则若在第10行设置断点,实际上还是在ShowMessage行,即原先的第10行。这就需要调试者小心一些,避免莫名其妙的干扰。比如明明修改了按钮的Caption,编译工程后却发现修改不起作用,还是显示原来的Caption。

3、 搜索路径顺序

见Delphi的Environment Options中的Library path,一般系统级的在前面,自定义的在后面。而若不填写Output directory,默认会把BPL输出到Delphi的Projects\Bpl目录下。因此,对于编译带包的工程来说,默认设置就OK了。注意Projects\Bpl的优先级要高于system32目录。我原来把Output directory改为system32目录,发现不管用,编译工程的时候还是会从Projects\Bpl取。对于Finalbuilder,由于搜索路径中没有Projects\Bpl,因此是从5资源\GrandResource D7\bpl取的。

4、 隐式包含

若BPL中引用了a单元,而a单元引用了b单元,则编译包时,实际上b也会被包含其中,并给出警告信息:“[Warning] Unit 'uLJJSDialog' implicitly imported into package 'GCMCommonPackage'”。由于代码的组织结构不完善,GCMCommonPackage包本应只包含一些公共单元,但实际由于这个原因,也包含了不少非公共的单元。像uJX_JXLBZDFrm、uFB_JSD_DBCLMXCXDlg等,这样是会产生一些隐患的。

5、 注意保持两个目录下包的一致性

若BPL重编了,而引用它的工程没有重编,常常会报错说找不到程序入口点,或者说某单元有个不同的版本。需要把工程也重编一下,注意保持system和Projects\BPL两个目录下包的一致性,因为运行带包的工程时,是会先找system32目录的,所以虽然编到Projects\BPL目录下,编译工程OK,但是运行时可能会出错。

6、 “never-build package XXX need always-build package YYY”

编译GCMCommonPackage时,常见的错误提示。解决办法是把YYY的选项“Rebuild as needed”改为勾选“Explicit rebuild”,然后重编YYY。具体原因我也没搞清楚。

7、 dcp文件的作用

简言之,dcp之对于bpl,类似于dcu之对于exe/dll。需要注意的是,如果编译带包的工程,则不仅需要BPL,同时也需要dcp。不过在运行工程时,只需要BPL即可。

8、 自动修改系统搜索路径

在包中引入单元文件后,Delphi会自动在Library Path中加上这个单元文件的路径,这就是为什么一开始我在本机上编译GCMCommonPackage时OK,但是在编译机上却总是找不到文件,搞得我莫名其妙,因为编译机无法自动添加Library Path。

下载公司材料报错

材料字典模块,点击“下载公司材料”按钮,弹出二级窗口后,下载或者直接取消,然后无论点什么按钮都报错,如下图所示:

http://115.img.pp.sohu.com/images/blog/2007/6/22/14/2/113eaf0d5f9.JPG

报错后,GCM无法关闭,只能杀进程。而如果不带GCMCommonPackage包,就一切OK。

由于对BPL了解不够深入,加上上述一些注意事项没有注意,此外似乎还有一些莫名其妙的原因(说到这里我还真得有点怀疑我的Delphi有点问题,一直报各种莫名其妙错误,无法顺利调试,我下午差点重装Delphi),对这个bug,跟踪了两三天,想了各种方法,一直找不到问题所在,束手无策。后来今天下午不知道怎么,Delphi忽然肯合作了,让我能够顺利设置断点调试,定位到了问题所在:创建二级窗体后,原窗体的GCMInfo.cdsTableInfo变为nil了,所以后面在对cdsTableInfo操作时,会报越界错误。我跟了N次,却找不到它是何时修改的。此时我已头晕眼花,筋疲力竭,于是请来黄山川和老贾帮我调试,老贾不愧牛人,想到一招:设置地址断点,于是乎,顺利找到了修改处(这里不得不再次感谢Delphi的合作),原来这里有一个IFNDEF dllmode,我这才恍然大悟,原来编译GCMCommonPackage的时候,根本没想到设置dllmode编译选项。没想到这里居然会有影响!于是加上了dllmode编译选项,compile……怎么还报错?原来是没覆盖dcp文件,赶紧覆盖一下。再试一次,OK了!明天再测试一下新版本,应该没问题了。

由于CommonPackage编译时加了dllmode选项,所以只适用于dll,而不适用于其它的工程,比如主窗体,项目管理工具,其它工具等,否则也会报错(因为它们需要执行IFNDEF处的代码)。所以最终决定编译dll使用这个包,而编译主窗体和项目管理工具等时,不带这个包,当然也可以创建两个BPL,一个带dllmode选项,一个不带dllmode选项,编译dll或exe时,根据情况决定用哪个包。

COM注册失败

设置好GCM与GCC的关联后,在项目基本信息模块,点击“上报”按钮,报错如下:

http://117.img.pp.sohu.com/images/blog/2007/6/22/14/1/113eae92278.jpg

既然提示检查设置,那就先看看设置,经详细检查,排除了设置的问题。再仔细看错误信息:没有注册类别。于是到GCC的COM里面去跟踪调试,后来发现是一个COM组件没有注册:pdoSJSB3_GCC.dll。注册COM组件应该是在安装时,自动注册的。尝试到组件服务中手工注册,结果注册失败,如下图所示:

http://115.img.pp.sohu.com/images/blog/2007/6/22/14/1/113eaef56e9.jpg

对这个问题,找了半天也找不到问题出在哪里。于是把Jiayp拉过来看,试了半天……还是不行。主要认为是它引用的包没有找到。Jiapy又把Linc拉过来看,使用了一些工具,像Dependency、PE Explorer等,可忙了半天也查不到问题。后来Linc想到一个方法:直接LoadLibrary,报如下的错误:

http://117.img.pp.sohu.com/images/blog/2007/6/22/14/29/113eae75bcb.jpg

一开始认为是GCMCommonPackage.BPL与pdoSJSB3_GCC.dll版本不兼容,后来发现即使是从编辑机编出来的gip包里取这两文件,还是一样报错。这就排除了版本的原因。

仔细看一下这个错误,意思是在Udocommon3executor.pas单元,找不到TdoCommon3Executor.CreateDOData方法。

于是查看udoCommon3executor.pas单元,按Ctrl+G,果然找不到CreateDOData方法,再仔细一看,原来是有这个方法的,只不过是抽象方法:

function CreateDOData(ADBInfo: OleVariant; AClientType: Integer; AOwnerData: OleVariant): TCommonDOData; virtual; abstract;

我猜想编译BPL包的时候,没有把抽象方法处理进去,而注册COM的时候,又需要该方法,所以报这个错误。具体原因还没有深究。解决方法是把该方法改为空的虚方法而非抽象方法,试验了一下,果然OK了。

后来Jiapy又提示我,可能是因为子类调用了inherited CreateDOData,我找了一下,果然在udoSJSB3Executor_GCC.pas中,有如下代码:

Result := inherited CreateDOData(ADBInfo, AClientType, AOwnerData);

明明父类是抽象方法,这里怎么还调用呢。于是把该处代码注释掉。果然,父类改回抽象方法,也OK了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Delphi 是一款非常流行的编程语言,利用它可以快速开发各种 Windows 应用程序,尤其是需要图形界面的应用。同时,DelphiBPL 功能也为开发者带来了很大的方便。BPL(Borland Package Library)是 Delphi 的一种动态链接库,可以将一些共用的代码进行打,形成一个“”,当其它程序需要调用这些代码时,只需将这个导入,就可以直接使用其中的函数和类库。 在 Delphi 中,我们可以利用 BPL 将一些共享的窗体封装起来,形成一个共享基础窗体。这样,在其他程序中只需导入这个 BPL 就可以调用这些窗体。具体实现步骤如下: 1. 创建一个 Delphi 应用程序,含需要共享的基础窗体代码,例如项目文件 main.dfm 和 main.pas。 2. 在主界面的“Project”菜单中选择“Add New” 加入一个新的 BPL 项目,例如 named 'basic.bpl'。 3. 在添加的BPL 项目的菜单中,选择“Project” -> “Options” -> "Description" ,在框中输入项目描述并勾选“build with runtime packages”选项,然后保存设置。 4. 对 BPL 名称空间进行设置,以便在其他应用程序中进行调用。在 BPL 项目文件中的菜单中选择“Project” -> “Options” -> "Packages",点击“Add” 按钮选择使用 BPL 的应用程序,例如 named 'app.exe'。 5. 在 BPL 项目文件的窗体单元 main.pas 中定义基础窗体,并创建需要在主窗口显示的控件。并将这些控件移植到别的窗体上。 通过上述步骤,我们就可以将基础窗体封装成一个 BPL ,在其他的应用程序中进行调用和使用。BPL 功能使得代码的共享和复用更加方便,可以有效提高开发效率和代码的可维护性,让 Delphi 编程变得更加简单。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值