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 的勾选以及里面填写的包的名字。