我们可以看到一个函数@Test@Register$QQrv。几乎可以肯定这个函数就是BPL把Test单元中的Register导出的注册函数,而那个@Testbtn@Register$qqrv就一定是Testbtn这个单元的注册函数。可以做一个实验来证明我们的想法,在Test单元的Register的函数中加上ShowMessage(‘你好,你调用了注册函数’); 然后在我们来调用一下包中的函数@Test@Register$qqrv,随便写一个工程看看是不是可以调用得到Test单元中的Register过程。 var H : Integer; regproc : procedure(); begin H := 0; H := LoadPackage('TestPackage.bpl'); try if H <> 0 then begin RegProc := GetProcAddress(H,'@Test@Register$qqrv');//载入包中的函数 if Assigned(RegProc) then begin regproc();//调用函数 end; end; finally if H <> 0 then begin UnloadPackage(H); H := 0; end; end; end; 调用的结果,果然调用到了包中Terst单元的Register过程。但是如何得到注册了哪些类呢?注册组件要用RegisterComponents函数。好在VCL体系的源代码是开放的,我们看看RegisterComponents是如何实现的吧。 在Classes单元我们可以看到: procedure RegisterComponents(const Page: string; const ComponentClasses: array of TComponentClass); begin if Assigned(RegisterComponentsProc) then RegisterComponentsProc(Page, ComponentClasses) else raise EComponentError.CreateRes(@SRegisterError); end; 画线的是一个函数指针,Delphi的IDE就是在这个指针所指的函数里去作具体的工作。我们也可以利用它来实现我们的注册。 procedure MyRegComponentsProc(const Page: string; const ComponentClasses: array of TComponentClass); var I : Integer; IDEInfo : PIDEInfo; begin for i := 0 to High(ComponentClasses) do begin RegisterClass(ComponentClasses[I]); end; end; 然后一条语句RegisterComponentsProc:= @MyRegComponentsProc;似乎就解决问题了。 慢着!RegisterComponentsProc是在Classes单元。但是BPL中的Classes单元是在另一个运行时的包VCL.BPL里面。而我们工程所修改的RegisterComponentsProc的指针是编译在我们的工程中,空间是不同的。所以我们的工程一定要编译成带运行时包VCL.BPL的才行。但是这样一来的话我们也就只能载入和我们所用的编译器相同版本编译器编译出来的BPL文件了,也就是说Delphi6只能载入Delphi6或者BCB6编译出来的BPL文件以此类推。 但是还有一个问题没有解决,那就是如何知道一个包中到底有那些各单元呢?可以通过GetPackageInfo过程来获得。 我已经把加载包的过程封装到了一个类中。整个程序的代码如下:
procedure RegComponentsProc(const Page: string; const ComponentClasses: array of TComponentClass); var I : Integer; IDEInfo : PIDEInfo; begin for i := 0 to High(ComponentClasses) do begin RegisterClass(ComponentClasses[I]); new(IDEInfo); IDEInfo.iPage := Page; IDEInfo.iClass := ComponentClasses[I]; CurrentPackage.FPageInfos.Add(IDEInfo); end; end;
procedure EveryUnit(const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer); begin case NameType of ntContainsUnit: CurrentPackage.FContainsUnit.Add(Name); ntDcpBpiName: CurrentPackage.FDcpBpiName.Add(Name); ntRequiresPackage: CurrentPackage.FRequiresPackage.Add(Name); end; end; { TPackage }
constructor TPackage.Create(const FileName: string); begin FPackageFileName := FileName; LoadPackage; end;
procedure TPackage.ClearPageInfo; var I:Integer; IDEInfo:PIDEInfo; begin for i:=FPageInfos.Count-1 downto 0 do begin IDEInfo:=FPageInfos[I]; Dispose(IDEInfo); FPageInfos.Delete(I); end; FPageInfos.Clear; end;
constructor TPackage.Create(const PackageHandle: THandle); begin FPackageFileName := GetModuleName(PackageHandle); LoadPackage; end;
destructor TPackage.Destroy; var I : Integer; begin FContainsUnit.Free; FRequiresPackage.Free; FDcpBpiName.Free; if FPackHandle <> 0 then begin UnRegisterModuleClasses(FPackHandle); ClearPageInfo; FPageInfos.Free; UnloadPackage(FPackHandle); FPackHandle := 0; end; inherited Destroy; end;
function TPackage.GetIDEInfoCount: Integer; begin Result := FPageInfos.Count; end;
function TPackage.GetIDEInfo(Index: Integer): TIDEInfo; begin if (Index in [0..(FPageInfos.Count - 1)]) then begin Result := TIDEInfo(FPageInfos[Index]^); end; end;
procedure TForm1.Button1Click(Sender: TObject); var I : Integer; begin if OpenDialog1.Execute then begin FreePack; FPack := TPackage.Create(OpenDialog1.FileName); FPack.RegClassInPackage; end; ListBox1.Items.Clear; for i := 0 to FPack.IDEInfoCount - 1 do begin ListBox1.Items.Add(FPack.IDEInfo[I].iClass.ClassName); end; Memo1.Lines.Clear; Memo1.Lines.Add('------ContainsUnitList:-------'); for i := 0 to FPack.ContainsUnit.Count - 1 do begin Memo1.Lines.Add(FPack.ContainsUnit[I]); end; Memo1.Lines.Add('------DcpBpiNameList:-------'); for i := 0 to FPack.DcpBpiName.Count - 1 do begin Memo1.Lines.Add(FPack.DcpBpiName[I]); end; Memo1.Lines.Add('--------RequiresPackageList:---------'); for i := 0 to FPack.RequiresPackage.Count - 1 do begin Memo1.Lines.Add(FPack.RequiresPackage[I]); end; end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin FreePack; end;
procedure TForm1.Button2Click(Sender: TObject); var Ctrl : TControl; begin if (ListBox1.ItemIndex <> -1) and (FPack <> nil) then begin //判断如果不是TControl的子类创建了也看不见,就不创建了 if (FPack.IDEInfo[ListBox1.ItemIndex].iClass.InheritsFrom(TControl)) then begin Ctrl := nil; try Ctrl := TControl(FPack.IDEInfo[ListBox1.ItemIndex].iClass.Create(Self)); Ctrl.Parent := Panel1; Ctrl.SetBounds(0, 0, 100, 100); Ctrl.Visible := True; except
end; end; end; end;
procedure TForm1.FreePack; var I : Integer; begin for i := Panel1.ControlCount - 1 downto 0 do Panel1.Controls[i].Free; FreeAndNil(FPack); end;
end. 窗体文件如下: object Form1: TForm1 Left = 87 Top = 120 Width = 518 Height = 375 Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False OnClose = FormClose PixelsPerInch = 96 TextHeight = 13 object GroupBox1: TGroupBox Left = 270 Top = 0 Width = 240 Height = 224 Align = alRight Caption = '类' TabOrder = 0 object ListBox1: TListBox Left = 2 Top = 15 Width = 236 Height = 207 Align = alClient ItemHeight = 13 TabOrder = 0 end end object Panel1: TPanel Left = 0 Top = 224 Width = 510 Height = 124 Align = alBottom Color = clCream TabOrder = 1 end object Button1: TButton Left = 8 Top = 8 Width = 249 Height = 25 Caption = '载入包' TabOrder = 2 OnClick = Button1Click end object Button2: TButton Left = 8 Top = 40 Width = 249 Height = 25 Caption = '创建所选中的类的实例在Panel上' TabOrder = 3 OnClick = Button2Click end object Memo1: TMemo Left = 8 Top = 72 Width = 257 Height = 145 ReadOnly = True ScrollBars = ssBoth TabOrder = 4 end object OpenDialog1: TOpenDialog Filter = '*.BPL|*.BPL' Left = 200 Top = 16 end end 在这些基础上我们完全可以建立一个自己的Delphi的IDE,对象的属性的获得和设置用TYPInfo单元的RTTI类函数完全可以轻松搞定,我就不在这里多费口舌了。 记住了,编译时一定要用携带VCL.BPL 包的方式.