Delphi 中 BPL(1):基本概念、用途及使用方法

近期,想将原有Delphi编写的MIS软件进行深耕和重构,其中准备采用BPL组件的形式以缩小主程序的大小,增强软件的灵活性和可扩展性。因此,开始学习和了解DELPHI的BPL组件,将相关的学习资料进行归类系统化,既备自己不时之需,同时也供相关同仁借鉴和参考。

Delphi 中 BPL 基本概念、用途及使用方法

1. BPL 文件简介

BPL 是 Binary Package Library 的缩写,表示二进制包文件。它是 Delphi 和 C++Builder 中用来存储运行时包的一种形式。这些包可以包含 VCL 或者自定义组件及其相关资源。

运行时包允许开发者将某些功能模块化并共享给多个项目使用。
它们减少了最终应用程序的体积,因为公共代码只需加载一次即可供多个应用访问。

2. BPL 文件的主要用途
(1) 减少可执行文件大小

通过将常用的功能提取到独立的动态链接库(DLL)或者运行时包中,开发人员能够显著减少主程序的尺寸。例如,在早期版本的 Delphi 中,VCL 向每个 EXE 添加至少 150KB 到 200KB 的基础代码。如果采用 DLL,则该 DLL 自身以及任何依赖它的程序都会重复携带这部分冗余数据。而利用 BPL 技术,这种问题得到了有效缓解。

(2) 提高重用性和维护效率

当多个应用程序都需要相同的一组特性或控件时,可以把它们封装成一个单独的包而不是硬编码在各个项目的源码里。这样不仅方便更新修复错误也简化了跨不同解决方案间的协作流程。

(3) 支持插件架构设计模式

许多现代软件采用了灵活扩展机制——即所谓的“插件”。在这种场景下,核心框架保持不变的同时允许第三方贡献额外能力而不必重新构建整个系统结构。借助于支持延迟绑定特性的 BPLs ,就可以轻松实现这一目标。

3. 如何生成 BPL 文件?

要创建一个新的运行时包 (.bpl),请按照以下步骤操作:

在 IDE 菜单栏选择 File -> New 并点击 Other....
展开左侧列表找到 “Package” 类别下的模板项 (Runtime Package) 然后双击确认新建.

此时会弹出对话框询问是否启用特定设置比如调试信息等,默认勾选通常就足够满足大多数需求了.

接着可以在右侧窗口拖拽所需的单元加入当前工程范围之内; 另外还需要注意的是确保所有必要的依赖关系都被正确声明出来以免编译失败.

最后保存修改后的配置并通过标准方式完成构建过程(Ctrl+F9). 成功之后应该能在指定目录发现对应名称结尾为 .bpl 的新产物实例存在.

以下是简单的脚本演示如何自动化部分环节:

program CreateMyPackage;

uses
  SysUtils, Classes;

var
  ProjectName : String;
begin
  try
    Writeln('Enter your package name:');
    Readln(ProjectName);
    
    // Simulate command line arguments passed to DPR file when creating a new runtime package project via CLI tools like MSBuild etc...
    Executable.ProgramCommandLine := Format('--Create "%s.bdsproj"', [ProjectName]);
    
    // Call internal procedure responsible for handling actual creation logic based on provided parameters above ...
    Internal_CreateNewRuntimePackage(Executable.ProgramCommandLine);

    writeln('Your package has been successfully created.');
  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

以上只是一个伪代码片段示意可能存在的接口调用逻辑,并不实际可用作生产环境部署方案的一部分。

4.在 Delphi 应用程序中加载和卸载 BPL 的方法
(1) 动态加载 BPL 文件

动态加载 BPL 文件是指在运行时将包文件加载到内存中以便使用其中的功能。这种方法提供了灵活性,即使缺少某个 BPL 文件也不会阻止应用程序启动。

动态加载可以通过函数 LoadPackage 实现。此函数返回一个句柄,用于后续的操作。
下面是一个典型的例子展示如何加载名为 package1.bpl 的包文件:

function LoadBPL(const FileName: string): HMODULE;
var
  ErrorMsg: array[0..255] of Char;
begin
  Result := LoadPackage(PChar(FileName));
  if Result = 0 then
  begin
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
                  nil, GetLastError, LANG_NEUTRAL, @ErrorMsg, SizeOf(ErrorMsg), nil);
    raise Exception.CreateFmt('Failed to load %s: %s', [FileName, ErrorMsg]);
  end;
end;
(2) 从已加载的 BPL 创建类实例

一旦 BPL 被成功加载,就可以从中获取类定义并创建对象实例。这通常涉及查找特定类型的类名,并通过 RTTI(运行时类型信息)机制实例化它。

使用 GetClass 函数可以根据字符串形式的类名检索对应的类描述符。
接下来可以用这个类描述符来分配新的对象实例。

下面的例子展示了如何从刚加载的 BPL 中取得某类的对象实例:

procedure TForm3.Button1Click(Sender: TObject);
var
  theClass: TPersistentClass;
  thePlugin: TPersistent;
  IPlug: IPlugin;
  FPackege: Cardinal;
begin
  FPackege := LoadPackage('package1.bpl'); // 加载包
  try
    theClass := GetClass('TMyPersistent');
    if Assigned(theClass) and (theClass.InheritsFrom(TPersistent)) then
    begin
      thePlugin := TMyPersistentClass(theClass).Create(nil); // 创建对象
      try
        if Supports(thePlugin, IPlugin, IPlug) then
          IPlug.Execute; // 假设 Execute 是接口的一个方法
      finally
        thePlugin.Free;
      end;
    end;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
end;
(3) 卸载 BPL 文件

当不再需要某个 BPL 提供的服务时,应当将其安全地卸载以释放资源。这可通过调用 UnloadPackage 来完成,传入之前由 LoadPackage 返回的句柄作为参数。

需要注意的是,在卸载之前必须确保没有任何活动引用指向该包内的任何实体,否则可能导致崩溃或其他异常行为。

这里给出一段示范代码说明怎样正确地卸载先前加载过的 BPL:

procedure UnloadBPL(ModuleHandle: HMODULE);
var
  ErrorMsg: array[0..255] of Char;
begin
  if not UnloadPackage(ModuleHandle) then
  begin
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
                  nil, GetLastError, LANG_NEUTRAL, @ErrorMsg, SizeOf(ErrorMsg), nil);
    raise Exception.CreateFmt('Failed to unload package with handle %d: %s',
                             [ModuleHandle, ErrorMsg]);
  end;
end;

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容