动态加载和动态注册类技术的深入探索

fazeramor5

fazeramor5

2016-02-19 12:52

岁数大了,QQ也不闪了,微信也不响了,电话也不来了,但是图老师依旧坚持为大家推荐最精彩的内容,下面为大家精心准备的动态加载和动态注册类技术的深入探索,希望大家看完后能赶快学习起来。
Delphi的包是Delphi IDE的核心技术,没有包也就没有了Delphi的可视化编程。包也可以用在我们开发的项目中,其好处是可以代码共享,减小工程尺寸,单纯通过替换包文件就能实现工程的升级和补丁。但是我们要加载包,就要知道包中已经存在的类。关于如何动态加载包的资料比比皆是我就不想就此问题讨论了。但是Delphi的IDE很是特殊,它无需事先知道你的包有哪些类就能注册组建,创建组建。但是Borland没有公开BPL文件的格式。我们自己是否可以实现IDE的功能呢?
  首先我们知道。一个组件包想要能在IDE中使用就要进行注册也就是要创建一个过程例如:
  Procedure Register;
  Begin
     RegisterComponents(IDE中的页面, [组件类]);
  End;
  在IDE加载时就要调用这个过程进行注册。
  其次我们通过Borland的文档又知道BPL只是一种特殊格式的DLL文件。那么既然IDE可以调用得到注册过程那么注册过程一定要是导出类型(exports)的才行。既然如此我们可以想办法弄明白。写一个包文件。里面包含Test、和TestBtn两个单元。两个单元分别都有注册过程,然后编译成BPL文件。好了我们可以用EXESCOPE这个工具来弄清楚其中的奥秘。
  
  我们可以看到一个函数@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过程来获得。
  我已经把加载包的过程封装到了一个类中。整个程序的代码如下:
  
  { *********************************************************************** }
  {                                                                         }
  { 动态加载Package的类                                                     }
  {                                                                         }
  { wr960204(王锐)2003-2-20                                                 }
  {                                                                         }
  { *********************************************************************** }
  unit UnitPackageInfo;
  
  interface
  uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;
  type
    PIDEInfo = ^TIDEInfo;
    TIDEInfo = record
      iClass: TComponentClass;
      iPage: string;
    end;
  type
    TPackage = class(TObject)
    private
      FPackHandle: THandle;
      FPackageFileName: string;
      FPageInfos: TList;
      FContainsUnit: TStrings;            //单元名
      FRequiresPackage: TStrings;         //需要的的包
      FDcpBpiName: TStrings;              //
      procedure ClearPageInfo;
      procedure LoadPackage;
      function GetIDEInfo(Index: Integer): TIDEInfo;
      function GetIDEInfoCount: Integer;
    public
      constructor Create(const FileName: string); overload;
      constructor Create(const PackageHandle: THandle); overload;
      destructor Destroy; override;
      function RegClassInPackage: Boolean;
  
      property IDEInfo[Index: Integer]: TIDEInfo read GetIDEInfo;
      property IDEInfoCount: Integer read GetIDEInfoCount;
      property ContainsUnit: TStrings read FContainsUnit;
      property RequiresPackage: TStrings read FRequiresPackage;
      property DcpBpiName: TStrings read FDcpBpiName;
    end;
  implementation
  
  var
    CurrentPackage    : TPackage;
  
  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 TPackage.LoadPackage;
  var
    Flags             : Integer;
    I                 : Integer;
    UnitName          : string;
  begin
    FPageInfos := TList.Create;
    FContainsUnit := TStringList.Create;
    FRequiresPackage := TStringList.Create;
    FDcpBpiName := TStringList.Create;
    FPackHandle := SysUtils.LoadPackage(FPackageFileName);
    CurrentPackage := Self;
    GetPackageInfo(FPackHandle, @FPackHandle, Flags, EveryUnit);
  end;
  
  function TPackage.RegClassInPackage: Boolean;
  //该函数只能在工程文件需要VCL,RTL两个包文件时才能用
  //因为我们需要把全局的函数指针Classes.RegisterComponentsProc指向我们自己
  //函数(该函数为IDE准备,IDE会为它设定函数而我们的程序也要模仿IDE为它设定函数)。
  //如果不是带VCL和RTL两个包,那么我们设置的只是我们本身Classes单元的函数指针
  //而不是包括Package的全局的。
  //
  //而有趣的是如果我们的工程不带包运行,那么我们基本上可以同时用它来查看最近几个版本的
  //Borland编译器所产生的包文件而不会产生异常,但是控件不能够注册了。
  var
    I                 : Integer;
    oldProc           : Pointer;
    RegProc           : procedure();
    RegProcName, UnitName: string;
  begin
    oldProc := @Classes.RegisterComponentsProc;
    Classes.RegisterComponentsProc := @RegComponentsProc;
    FPageInfos.Clear;
    try
      try
        for i := 0 to FContainsUnit.Count - 1 do
        begin
          RegProc := nil;
          UnitName := FContainsUnit[I];
          RegProcName := '@' + UpCase(UnitName[1])
            + LowerCase(Copy(UnitName, 2, Length(UnitName))) + '@Register$qqrv';
          //后面这个字符串@Register$qqrv是Borland定死了的,Delphi5,6,7,BCB5,6都是这样子的
          //Delphi3是Name + '.Register@51F89FF7'。而Delphi4手里没有,不曾试验过
          RegProc := GetProcAddress(FPackHandle,
            PChar(RegProcName));
          if Assigned(RegProc) then
          begin
            CurrentPackage := Self;
            RegProc;
          end;
        end;
      except
        UnRegisterModuleClasses(FPackHandle);
        ClearPageInfo;
        Result := True;
        Exit;
      end;
    finally
      Classes.RegisterComponentsProc := oldProc;
    end;
  end;
  
  end.
  调用如下
  { *********************************************************************** }
  {                                                                         }
  { 程序主窗体单元                                                          }
  {                                                                         }
  { wr960204(王锐)2003-2-20                                                 }
  {                                                                         }
  { *********************************************************************** }
  unit Unit1;
  
  interface
  
  uses
    UnitPackageInfo,
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls, ExtCtrls;
  
  type
    TForm1 = class(TForm)
      GroupBox1: TGroupBox;
      Panel1: TPanel;
      ListBox1: TListBox;
      Button1: TButton;
      Button2: TButton;
      OpenDialog1: TOpenDialog;
      Memo1: TMemo;
      procedure Button1Click(Sender: TObject);
      procedure FormClose(Sender: TObject; var Action: TCloseAction);
      procedure Button2Click(Sender: TObject);
    private
      { Private declarations }
      FPack: TPackage;
      procedure FreePack;
    public
      { Public declarations }
    end;
  
  var
    Form1             : TForm1;
  
  implementation
  
  {$R *.dfm}
  
  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 包的方式.
展开更多 50%)
分享

猜你喜欢

动态加载和动态注册类技术的深入探索

编程语言 网络编程
动态加载和动态注册类技术的深入探索

动态加载iframe

Web开发
动态加载iframe

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

动态加载类的原理——元数据的使用

电脑网络
动态加载类的原理——元数据的使用

深入讲解JSP 2.0下的动态内容缓存技术

Web开发
深入讲解JSP 2.0下的动态内容缓存技术

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

动态CSS 换肤技术

电脑网络
动态CSS 换肤技术

动态CSS换肤技术

Web开发
动态CSS换肤技术

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

超强图片数量上传无限制

超强图片数量上传无限制

Access97的报表解决方案

Access97的报表解决方案
下拉加载更多内容 ↓