BPL 是一种特别的 DLL;

使用 Delphi 开发程序,可以把一个大程序的一部分,独立编译成一个 BPL,一个 Delphi 里面称作 Package 的东西,这里中文我们称作【包】,然后让 EXE 去加载它。

一个 EXE 加载 BPL 有两种模式:1. 静态加载;2. 动态加载;

关于静态加载:

EXE 静态加载一个 BPL,如果 EXE 在启动的时候系统里面没有正确的 BPL 文件,EXE 会启动失败。

关于动态加载:

动态加载有很多好处。比如,BPL 文件不存在的时候,EXE 不会启动不成功;比如,EXE 可以在运行期,动态加载 / 卸载 BPL 包。既然可以在运行期卸载后再重新加载,就可以在运行期动态替换不同的 BPL 包获得不同的功能。这也是一些插件的实现方式。

当然,插件的方式,采用 DLL 也是可以的。但 Delphi 调用 DLL,就没法使用 DLL 内部的 Delphi 对象,因为 DLL 是一个 WINDOWS 底下的通用标准,C / C++ 也是能够用到。采用 Delphi 自己的 BPL 则可以直接访问里面的 Delphi 类 / 对象。

动态加载 BPL 的几个注意要点:

动态加载 BPL 有两种方式:1. 通用方式;2. 基于 RTTI;

1. 通用方式,先看加载的代码:

procedure TForm1.Button2Click(Sender: TObject);
var
  S: string;
begin
  case RadioGroup1.ItemIndex of
    0: S := 'MyBPL1.bpl';

    1: S := 'MyBPL2.BPL';
  end;
  PackageModule := LoadPackage(S);

  AClass := GetClass('TFmBPLMain');   //这里使用 GetClass 无法找到类。但直接使用类是可以的。 // 无法找到类的原因是这个 EXE 没有勾选 RunTimePackages

  if Assigned(AClass) then
  begin
    AForm := TComponentClass(AClass).Create(Application) as TForm;
    AForm.Show;
  end;
ene;

上述代码有两个需要注意的前提条件:

A. 这个加载 BPL 的 EXE 程序,必须在其 Options / Packages / Link with Rum Time Packages 的菜单里面,勾选;通常情况下,勾选后,Delphi IDE 会自动在下面的 Run Time Packages 里面,自动填入一大堆【包】的名字,基本上就是一个 Delphi 程序运行所需要的所有的包。这样编译出来的 EXE 特别小,但这个 EXE 在发布到其它电脑上的时候,需要记得把 Delphi 带来的这些包一起拷贝发布过去,否则 EXE 不能正常启动。

A.1. 如果上述 Run Time Packages 里面的包的名字,都删除掉,编译出来的 EXE 会比较大,也能正常运行,也能正常 LoadPackage 加载 BPL 文件成功,但是,AClass = GetClass('TFmBPLMain'); 这一句会返回 nil,找不到这个类。

B. GetClass 能够成功的第二个前提:BPL 里面的这个 TFmBPLMain 的代码里面,需要自动注册这个类,才可以根据类名称字符串找到类类型。注册的代码是:initialization RegisterClass(TFmBPLMain); 

要点:BPL 里面的这个 TFmBPLMain 必须在它的 initialization 里面,注册这个类,EXE 里面的 GetClass 函数才可以通过类名称字符串找到这个类。否则返回 nil;

常见问题:如果代码都没问题,但 GetClass 返回 nil,检查 1. BPL 里面有没有注册类;2. EXE 的 RunTime Package 是否打勾,如果打勾,则检查填写的包名称是否有遗漏。

=======================

2. RTTI 方式,先看代码:

procedure TForm2.Button1Click(Sender: TObject);
var
  S: string;

  LContext: TRttiContext;
  LPackage: TRttiPackage;
  LClass: TRttiInstanceType;
  aForm: TForm;
  unitClass: string;

  H: HINST;
  RT: TRttiType;
begin
  S := 'MyBPL1.bpl';
  PackageModule := LoadPackage(S);
  if PackageModule = 0 then raise Exception.Create('加载 BPL 失败!');
 //==========================

  unitClass := 'UFmMainBPL.TFmBPLMain';
  //unitClass := 'TFmBPLMain';    //只写类名,LPackage.FindType 返回 nil;

  {------------------------------------------------------------------------------
    以下代码测试通过。
    在调试以下代码时碰到的问题:
    1. 在这个 EXE 的 Options /  RunTimePackage 里面,把系统自动加载的包的名字全部去掉,则 LContext.GetPackages 只有一个包,就是这个 EXE 自己;
       在 RunTimePackage 里面加上 rtl 包的名字以后,才能找到加载的 BPL;

    2. 找到加载的 BPL 以后,通过 'UFmMainBPL.TFmBPLMain' 名字找不到对应的 Class。
       把系统自动加载的全部包名称都放进 RunTimePackage 里面以后,能找到并创建 BPL 里面的 Form 成功。

    上述测试的前提是 BPL 里面的 Form 有:initialization  RegisterClass(TFmBPLMain); 注册自己。

    测试将 BPL 里面的注册 RegisterClass(TFmBPLMain) 代码去掉,以下程序仍然可用。
  ------------------------------------------------------------------------------}

  AClass := GetClass('TFmBPLMain');

  LContext := TRttiContext.Create;
  try
    try
      RT := LContext.FindType(unitClass);
      for LPackage in LContext.GetPackages() do
      begin
        if SameText(ExtractFileName(LPackage.Name), S) then
        begin
          LClass := LPackage.FindType(unitClass) as TRttiInstanceType;

          aForm := LClass.MetaclassType.Create as TForm;
          aForm.Create(nil);
          aForm.WindowState := wsNormal;
          aForm.Position := poScreenCenter;
          aForm.Show;
        end;
      end;
    except
    ShowMessage('单元名和类名是大小写敏感的');
    end;
  finally
    LContext.Free;
  end;
end;

上述代码,通过 Delphi 的 RTTI 来获得加载的包里面的类;前提:

0. DELPHI XE 以上的版本。

1. BPL 里面的类,无需注册类,也即是无需有 initialization RegisterClass(TFmBPLMain);  这样的代码,通过 RTTI 也可以获得类。

2. EXE 必须勾选 Link with run time packages;

3. 勾选后,run time packages 里面填入的包名称要足够。如果缺少了必要的包名称,将会:

3.1. 缺少 RTL 则 LContext.GetPackages 里面没有加载成功的 BPL 包,只有 EXE 自己的名字;

3.2. 缺少其它的必要的包(暂时不清楚具体哪个包),则 LPackage.FindType 函数返回的 LClass 虽然不是 nil 但是类型不对,导致后面的创建 TForm 的代码会提示类型错误。

3.3. 把程序在勾选 Link with run time packages 时的默认全部包的名字都拷贝进去再重新编译运行,上述代码通过。

问题注意:因此,这里需要注意的问题仍然是检查这个 EXE 的 link with run time packages 的勾选以及里面填写的包的名字。